
    i                     h   d Z ddlZddlZddlZddlZddlmZ ddlmZm	Z	 ej                  j                  d e ee      j                  j                               ddlmZmZmZ d.dededz  defd	Zd.dededz  defd
Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      ZddlmZ  G d d      Z G d d      Z G d d      Z G d  d!      Z  G d" d#      Z! G d$ d%      Z" G d& d'      Z# G d( d)      Z$ G d* d+      Z% G d, d-      Z&y)/u   헤임달(개발2팀 테스터): codex_gate_check 테스트 선행 작성 — TDD RED 단계.

대상: /home/jay/workspace/scripts/codex_gate_check.py
함수: codex_gate_check(task_file, affected_files, workspace_root) -> dict
    N)Path)	MagicMockpatch)codex_gate_check_maat_fallback_check_normalize_affected_itemriskssuggestionsreturnc                 |    | |xs g d}t               }d|_        t        j                  |      |_        d|_        |S )u=   Codex CLI가 정상 반환하는 JSON stdout을 모킹한다.r	   r
   r    r   
returncodejsondumpsstdoutstderrr	   r
   payload	mock_procs       :/home/jay/workspace/scripts/tests/test_codex_gate_check.py_make_codex_resultr      F     "(bG IIzz'*II    c                 |    | |xs g d}t               }d|_        t        j                  |      |_        d|_        |S )u=   마아트 폴백이 반환하는 JSON stdout을 모킹한다.r   r   r   r   r   s       r   _make_maat_resultr   &   r   r   c                       e Zd ZdZd Zd Zy)TestCodexPassNoCriticalu3   critical severity 리스크가 없으면 gate PASS.c                 
   t        |dz        }t        |      j                  d       dddddddd	dg}t        |d
g      }t	        d|      5  t        |dgd      }d d d        d   du sJ d       y # 1 sw Y   xY w)Ntask.md   # 설계 문서highu   성능 저하 가능성severitydescriptionmediumu   로그 누락 우려lowu   주석 부족u   캐시 레이어 추가 검토r
   subprocess.runreturn_value
src/api.py/home/jay/workspace	task_fileaffected_filesworkspace_rootpassTu!   critical 없으면 PASS여야 함strr   
write_textr   r   r   selftmp_pathr0   r	   r   results         r   test_codex_pass_no_criticalz3TestCodexPassNoCritical.test_codex_pass_no_critical9   s    9,-	Y""#45  0IJ!2HI?

 'u;[:\]	#)< 	%# ,~4F	 f~%J'JJ%	 	s   A99Bc                     t        |dz        }t        |      j                  d       t        g g       }t	        d|      5  t        |g d      }ddd       d	   d
u sJ y# 1 sw Y   xY w)u#   리스크가 아예 없어도 PASS.r!   u   # 빈 설계r   r*   r+   r.   r/   Nr3   Tr4   r8   r9   r0   r   r:   s        r   test_codex_pass_empty_risksz3TestCodexPassNoCritical.test_codex_pass_empty_risksM   sv    9,-	Y"">2&RR@	#)< 	%#!4F	 f~%%%	 	s   A$$A-N)__name__
__module____qualname____doc__r;   r>    r   r   r   r   6   s    =K(&r   r   c                       e Zd ZdZd Zd Zy)TestCodexFailCriticalExistsu;   critical severity 리스크가 1개 이상이면 gate FAIL.c                     t        |dz        }t        |      j                  d       ddddddg}t        |      }t	        d|	      5  t        |d
gd      }d d d        d   du sJ d       y # 1 sw Y   xY w)Nr!   r"   criticalu   인증 우회 취약점r$   r'   u   로그 누락r*   r+   src/auth.pyr.   r/   r3   Fu%   critical 존재 시 FAIL이어야 함r4   r7   s         r   test_codex_fail_single_criticalz;TestCodexFailCriticalExists.test_codex_fail_single_criticald   s    9,-	Y""#45 $4MN!/B
 'u-	#)< 	%# -4F	 f~&O(OO&	 	s   A22A;c                 ,   t        |dz        }t        |      j                  d       ddddddg}t        |      }t	        d|      5  t        |d	d
gd      }ddd       d   du sJ t        d |d   D              }|dk(  sJ y# 1 sw Y   /xY w)u   critical 2개: 여전히 FAIL.r!      # 설계rG   u   SQL 인젝션r$   u   권한 상승 가능r*   r+   z	src/db.pyrH   r.   r/   Nr3   Fc              3   2   K   | ]  }|d    dk(  sd  yw)r%   rG      NrC   .0rs     r   	<genexpr>zQTestCodexFailCriticalExists.test_codex_fail_multiple_criticals.<locals>.<genexpr>   s     W11Z=J;VQWs   r	      )r5   r   r6   r   r   r   sum)r8   r9   r0   r	   r   r:   critical_counts          r   "test_codex_fail_multiple_criticalsz>TestCodexFailCriticalExists.test_codex_fail_multiple_criticalsw   s    9,-	Y"":. $OD#4JK
 'u-	#)< 	%# +];4F	 f~&&&WwWW"""	 	s   B

BN)r?   r@   rA   rB   rI   rU   rC   r   r   rE   rE   a   s    EP&#r   rE   c                       e Zd ZdZd Zd Zy)TestCodexTimeoutFallbackToMaatuU   subprocess.run이 TimeoutExpired를 발생시키면 마아트 폴백을 사용한다.c                     t        |dz        }t        |      j                  d       dddg}t        |dg      fd}t	        d	|
      5  t        |dgd      }d d d        d   dk(  sJ y # 1 sw Y   xY w)Nr!   r"   r(   u   폴백 경고r$   u   마아트 제안r)   c                      | r| d   n|j                  dg       }t        d |D              rt        j                  |d      S )Nr   argsc              3   6   K   | ]  }d t        |      v   ywcodexNr5   rO   cs     r   rQ   zjTestCodexTimeoutFallbackToMaat.test_codex_timeout_fallback_to_maat.<locals>.side_effect.<locals>.<genexpr>        27c!f$2      cmdtimeoutgetany
subprocessTimeoutExpiredrZ   kwargsre   	maat_procs      r   side_effectzWTestCodexTimeoutFallbackToMaat.test_codex_timeout_fallback_to_maat.<locals>.side_effect   sB    !$q'vzz&"'=C2c22 //CDDr   r*   ro   zsrc/service.pyr.   r/   sourcemaat_fallback)r5   r   r6   r   r   r   )r8   r9   r0   
maat_risksro   r:   rn   s         @r   #test_codex_timeout_fallback_to_maatzBTestCodexTimeoutFallbackToMaat.test_codex_timeout_fallback_to_maat   s    9,-	Y""#45 $)IJ
%j?Q>RS		 #= 	%# 014F	 h?222	 	s   A33A<c                 $   t        |dz        }t        |      j                  d       t        g g       fd}t	        d|      5  t        |g d      }d	d	d	       h d
}|j                  j                               sJ y	# 1 sw Y   /xY w)uP   타임아웃 폴백 결과도 유효한 딕셔너리 구조를 가져야 한다.r!   rK   r)   c                      | r| d   n|j                  dg       }t        d |D              rt        j                  |d      S )Nr   rZ   c              3   6   K   | ]  }d t        |      v   ywr\   r^   r_   s     r   rQ   ziTestCodexTimeoutFallbackToMaat.test_codex_timeout_result_is_valid.<locals>.side_effect.<locals>.<genexpr>   ra   rb   rc   rd   rg   rl   s      r   ro   zVTestCodexTimeoutFallbackToMaat.test_codex_timeout_result_is_valid.<locals>.side_effect   sB    !$q'vzz&"'=C2c22 //CDDr   r*   rp   r.   r/   N>   r3   errorr	   rq   r
   )r5   r   r6   r   r   r   issubsetkeys)r8   r9   r0   ro   r:   required_keysrn   s         @r   "test_codex_timeout_result_is_validzATestCodexTimeoutFallbackToMaat.test_codex_timeout_result_is_valid   s    9,-	Y"":.%bb9		 #= 	%#!4F	 L%%fkkm444	 	s   	BBN)r?   r@   rA   rB   rt   r|   rC   r   r   rW   rW      s    _305r   rW   c                       e Zd ZdZd Zd Zy)TestCodexApiErrorFallbackToMaatuV   Codex CLI가 비정상 종료(returncode != 0)하면 마아트 폴백을 사용한다.c                    t        |dz        }t        |      j                  d       t               d_        d_        d_        t               d_        t        j                  ddg ii      _        d_        fd	}t        d
|      5  t        |dgd      }d d d        d   dk(  sJ y # 1 sw Y   xY w)Nr!   rK   rM   r   API rate limit exceededr   blast_radiuscallersc                  b    | r| d   n|j                  dg       }t        d |D              rS S )Nr   rZ   c              3   6   K   | ]  }d t        |      v   ywr\   r^   r_   s     r   rQ   zmTestCodexApiErrorFallbackToMaat.test_codex_api_error_fallback_to_maat.<locals>.side_effect.<locals>.<genexpr>   ra   rb   )rh   ri   )rZ   rm   re   ast_proc
error_procs      r   ro   zZTestCodexApiErrorFallbackToMaat.test_codex_api_error_fallback_to_maat.<locals>.side_effect   s4    !$q'vzz&"'=C2c22!!Or   r*   rp   r-   r.   r/   rq   rr   )r5   r   r6   r   r   r   r   r   r   r   r   )r8   r9   r0   ro   r:   r   r   s        @@r   %test_codex_api_error_fallback_to_maatzETestCodexApiErrorFallbackToMaat.test_codex_api_error_fallback_to_maat   s    9,-	Y"":. [
 !

5
 ;**ny"o%FG	 #= 	%# ,~4F	 h?222	 	s   B66B?c                    t        |dz        }t        |      j                  d       t               }d|_        d|_        d|_        t        d|      5  t        |g d	      }d
d
d
       d   dk(  sJ |d   J y
# 1 sw Y   xY w)uk  API 오류 시 마아트 폴백으로 전환되며 source='maat_fallback'이어야 한다.

        구현체는 returncode != 0이면 즉시 _maat_fallback_check()를 호출한다.
        마아트 폴백 결과의 error 필드는 None으로 설정된다(폴백 자체는 성공).
        대신 source='maat_fallback'으로 오류 전환을 알린다.
        r!   rK   rM   r   zconnection refusedr*   r+   r.   r/   Nrq   rr   rx   	r5   r   r6   r   r   r   r   r   r   r8   r9   r0   r   r:   s        r   %test_codex_api_error_sets_error_fieldzETestCodexApiErrorFallbackToMaat.test_codex_api_error_sets_error_field   s     9,-	Y"":.[
 !

0
 #*= 	%#!4F	 h?222g&&&	 	s   A>>BN)r?   r@   rA   rB   r   r   rC   r   r   r~   r~      s    `3B'r   r~   c                   H    e Zd ZdZh dZd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zy)TestOutputJsonFormatuB   반환 딕셔너리가 명세된 스키마를 충족해야 한다.>   r3   rx   r	   rq   r
   c                 h   t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t        |dgd      }d d d        | j                  j                  j                               s%J d| j                  |j                         z
          y # 1 sw Y   XxY w)	Nr!   rK   r*   r+   zsrc/main.pyr.   r/   u   누락 키: )	r5   r   r6   r   r   r   REQUIRED_KEYSry   rz   r=   s        r   test_all_required_keys_presentz3TestOutputJsonFormat.test_all_required_keys_present  s    9,-	Y"":.&r*	#)< 	%# -4F	 !!**6;;=9n\$J\J\_e_j_j_lJlIm;nn9	 	s   B((B1c                     t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t        |g d      }d d d        t        d   t              sJ y # 1 sw Y   xY w)Nr!   rK   r*   r+   r.   r/   r3   )r5   r   r6   r   r   r   
isinstanceboolr=   s        r   test_pass_is_boolz&TestOutputJsonFormat.test_pass_is_bool  sv    9,-	Y"":.&r*	#)< 	%#!4F	 &.$///	 	s   A..A7c                     t        |dz        }t        |      j                  d       t        dddg      }t	        d|      5  t        |dgd	
      }d d d        t        d   t              sJ y # 1 sw Y   xY w)Nr!   rK   r#   u   경고r$   r*   r+   zsrc/x.pyr.   r/   r	   r5   r   r6   r   r   r   r   listr=   s        r   test_risks_is_listz'TestOutputJsonFormat.test_risks_is_list.  s    9,-	Y"":.&VH(U'VW	#)< 	%# *|4F	 &/4000	 	s   A33A<c                     t        |dz        }t        |      j                  d       t        g ddg      }t	        d|      5  t        |g d	      }d d d        t        d
   t              sJ y # 1 sw Y   xY w)Nr!   rK   u   제안 1u   제안 2r)   r*   r+   r.   r/   r
   r   r=   s        r   test_suggestions_is_listz-TestOutputJsonFormat.test_suggestions_is_list=  s~    9,-	Y"":.&r
J7OP	#)< 	%#!4F	 &/666	 	s   A22A;c                     t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t        |g d      }ddd       d   d	k(  sJ y# 1 sw Y   xY w)
u2   Codex 정상 응답 시 source는 codex_companion.r!   rK   r*   r+   r.   r/   Nrq   codex_companionr4   r=   s        r   test_source_is_codex_on_successz4TestOutputJsonFormat.test_source_is_codex_on_successL  sv    9,-	Y"":.&r*	#)< 	%#!4F	 h#4444	 	s   A##A,c                     t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t        |g d      }ddd       d   J y# 1 sw Y   xY w)	u   정상 응답 시 error=None.r!   rK   r*   r+   r.   r/   Nrx   r4   r=   s        r   test_error_is_none_on_successz2TestOutputJsonFormat.test_error_is_none_on_success\  sr    9,-	Y"":.&r*	#)< 	%#!4F	 g&&&	 	s   A  A)c                     t        |dz        }t        |      j                  d       ddddddg}t        |      }t	        d|	      5  t        |d
gd      }ddd       d   D ]  }d|v sJ d       d|v rJ d        y# 1 sw Y   *xY w)uB   리스크 항목은 severity, description 키를 가져야 한다.r!   rK   r#   u   고위험 항목r$   r(   u   저위험 항목r*   r+   zsrc/y.pyr.   r/   Nr	   r%   u"   risk 항목에 severity 키 없음r&   u%   risk 항목에 description 키 없음r4   )r8   r9   r0   r	   r   r:   items          r   +test_risk_item_has_severity_and_descriptionz@TestOutputJsonFormat.test_risk_item_has_severity_and_descriptionl  s    9,-	Y"":.  0BC/AB
 'u-	#)< 	%# *|4F	 7O 	RD%K'KK% D(Q*QQ(	R	 	s   BBc                 4   t        |dz        }t        |      j                  d       h d}ddddddd	d
ddddg}t        |      }t	        d|      5  t        |dgd      }ddd       d   D ]  }|d   |v rJ d|d            y# 1 sw Y   (xY w)u=   severity는 critical|high|medium|low 중 하나여야 한다.r!   rK   >   r(   r#   r'   rG   rG   u   치명r$   r#   u   높음r'   u   중간r(   u   낮음r*   r+   zsrc/z.pyr.   r/   Nr	   r%   u"   허용되지 않는 severity 값: r4   )r8   r9   r0   valid_severitiesr	   r   r:   r   s           r   test_severity_values_are_validz3TestOutputJsonFormat.test_severity_values_are_valid  s    9,-	Y"":.@#H=9!(;x8	
 'u-	#)< 	%# *|4F	 7O 	qD
#'77p;]^bcm^n]o9pp7	q	 	s   BBN)r?   r@   rA   rB   r   r   r   r   r   r   r   r   r   rC   r   r   r   r     s5    LGMo0175 ' R,qr   r   c                   D    e Zd ZdZh dZddZd Zd Zd Zd Z	d	 Z
d
 Zy)"TestMaatFallbackReturnsValidResultuV   마아트 폴백 경로에서도 완전한 result 딕셔너리를 반환해야 한다.>   r3   rx   r	   rq   r
   Nc           
         t        |dz        }t        |      j                  d       t               }d|_        d|_        d|_        t        d|      5  t        dt        d	 |D               ||xs g d
dd      5  t        |g d      cddd       cddd       S # 1 sw Y   nxY w	 ddd       y# 1 sw Y   yxY w)uL  Codex 오류를 유발하여 마아트 폴백을 강제하는 헬퍼.

        마아트 폴백은 파일 존재 여부를 직접 검사하므로 affected_files는
        실제 존재하는 파일만 넘기거나 빈 목록을 사용한다.
        task_file은 tmp_path 안에 생성하여 실제로 존재하게 한다.
        r!   rK   rM   r   zcodex unavailabler*   r+   z%codex_gate_check._maat_fallback_checkc              3   ,   K   | ]  }|d    dk(    yw)r%   rG   NrC   rN   s     r   rQ   zJTestMaatFallbackReturnsValidResult._force_maat_fallback.<locals>.<genexpr>  s     #TAAjMZ$?#Ts   rr   N)r3   r	   r
   rq   rx   r.   r/   )
r5   r   r6   r   r   r   r   r   ri   r   )r8   r9   rs   maat_suggestionsr0   r   s         r   _force_maat_fallbackz7TestMaatFallbackReturnsValidResult._force_maat_fallback  s     9,-	Y"":.[
 !

/
#*= 	7 ##T#T TT'#3#9r-!	  ('#%#8 	 	  	 	 	s$   )B5>B	B5B(	$B55B>c                     | j                  |g       }| j                  j                  |j                               sJ y )Nrs   )r   r   ry   rz   r8   r9   r:   s      r   (test_maat_fallback_has_all_required_keyszKTestMaatFallbackReturnsValidResult.test_maat_fallback_has_all_required_keys  s7    **8*C!!**6;;=999r   c                 >    | j                  |g       }|d   dk(  sJ y )Nr   rq   rr   r   r   s      r   *test_maat_fallback_source_is_maat_fallbackzMTestMaatFallbackReturnsValidResult.test_maat_fallback_source_is_maat_fallback  s*    **8*Ch?222r   c                 H    dddg}| j                  ||      }|d   du sJ y)u2   마아트 폴백에서도 critical 없으면 PASS.r#   u   마아트 고위험r$   r   r3   TNr   r8   r9   rs   r:   s       r   -test_maat_fallback_pass_true_when_no_criticalzPTestMaatFallbackReturnsValidResult.test_maat_fallback_pass_true_when_no_critical  s7    #):OPQ
**8
*Kf~%%%r   c                 H    dddg}| j                  ||      }|d   du sJ y)u/   마아트 폴백에서 critical 있으면 FAIL.rG   u   마아트 치명 오류r$   r   r3   FNr   r   s       r   +test_maat_fallback_pass_false_when_criticalzNTestMaatFallbackReturnsValidResult.test_maat_fallback_pass_false_when_critical  s7    #->WXY
**8
*Kf~&&&r   c                 d    ddddddg}| j                  ||      }t        |d         dk(  sJ y	)
uJ   마아트 폴백 리스크 목록이 결과에 그대로 담겨야 한다.r'   u   마아트 중간 위험r$   r(   u   마아트 낮은 위험r   r	   rR   Nr   lenr   s       r   "test_maat_fallback_risks_preservedzETestMaatFallbackReturnsValidResult.test_maat_fallback_risks_preserved  sK     "2KL/HI

 **8
*K6'?#q(((r   c                 ^    g }ddg}| j                  |||      }t        |d         dk(  sJ y)u=   마아트 폴백 제안 목록이 결과에 담겨야 한다.u   마아트 제안 Au   마아트 제안 B)rs   r   r
   rR   Nr   )r8   r9   rs   r   r:   s        r   (test_maat_fallback_suggestions_preservedzKTestMaatFallbackReturnsValidResult.test_maat_fallback_suggestions_preserved  sC    
02FG**8
]m*n6-()Q...r   N)r?   r@   rA   rB   r   r   r   r   r   r   r   r   rC   r   r   r   r     s.    `GM@:3&')/r   r   c                       e Zd ZdZd Zd Zy)TestCallersContextu?   _get_callers_context가 프롬프트에 통합되는지 검증.c                   	 t        |dz        }t        |      j                  d       |dz  }|j                          |dz  j                  d       t	               d_        t        j                  ddd	gd
dgdgddd      _        d_	        t        g g       	ddig 	fd}t        d|      5  t        |dgt        |            }ddd       d   du sJ t        d D              sJ y# 1 sw Y   'xY w)uI   AST callers 컨텍스트가 Codex 프롬프트에 포함되어야 한다.r!   r"   scriptsast_dependency_map.py# dummyr   r-   zserver.py:42zroutes.py:15	server.pyz	routes.pyztest_api.py   )r   direct_importers
test_filestotal_affectedchanged_filer   r   r)   nc                     	dxx   dz  cc<   | r| d   n|j                  dg       }t        d |D              rS t        |      }d|v r|j                  d      }|dz   t	        |      k  r_||dz      }dd l}|j                  j                  |      r8t        |dd	      5 }
j                  |j                                d d d        S S # 1 sw Y   S xY w)
Nr   rM   r   rZ   c              3   6   K   | ]  }d t        |      v   yw)ast_dependency_mapNr^   r_   s     r   rQ   zbTestCallersContext.test_callers_context_included_in_prompt.<locals>.side_effect.<locals>.<genexpr>  s     ?a'3q61?rb   --prompt-filerP   utf-8encoding)rh   ri   r   indexr   ospathisfileopenappendread)rZ   rm   re   cmd_listidxprompt_pathr   pf
ast_result
call_countcaptured_prompt_filescodex_results           r   ro   zOTestCallersContext.test_callers_context_included_in_prompt.<locals>.side_effect  s    sOq O!$q'vzz&"'=C?3??!!CyH(*nn_57S]*"*37"3Kww~~k2!+sWE D188CD<Ds   " CCr*   rp   r/   Nr3   Tc              3   $   K   | ]  }d |v  
 yw)	   호출됨NrC   )rO   contents     r   rQ   zMTestCallersContext.test_callers_context_included_in_prompt.<locals>.<genexpr>)  s     Og;')Os   )r5   r   r6   mkdirr   r   r   r   r   r   r   r   r   ri   )
r8   r9   r0   scripts_dirro   r:   r   r   r   r   s
         @@@@r   'test_callers_context_included_in_promptz:TestCallersContext.test_callers_context_included_in_prompt  s(   9,-	Y""#45 *	.	.::9E [
 !
 JJ , .?)4k(B#0/&'	!


 
 *""=1X
 "	 $ #= 	%# ,~"8}F	 f~%%%O9NOOOO	 	s   =C<<Dc                     t        |dz        }t        |      j                  d       t        g g       }t	        d|      5  t        |dgd      }d	d	d	       d
v sJ d|v sJ y	# 1 sw Y   xY w)uQ   AST 스크립트 실패 시에도 codex_gate_check는 정상 동작해야 한다.r!   r"   r)   r*   r+   znonexistent.py/tmp/nonexistentr/   Nr3   rq   r4   )r8   r9   r0   r   r:   s        r   ,test_callers_context_fallback_on_ast_failurez?TestCallersContext.test_callers_context_fallback_on_ast_failure+  s    9,-	Y""#45 *""=#,? 	%# 011F	 6!!!	 	s   A((A1N)r?   r@   rA   rB   r   r   rC   r   r   r   r     s    I;Pz"r   r   _get_callers_contextc                   "    e Zd ZdZd Zd Zd Zy)TestGetCallersContextu4   _get_callers_context 헬퍼 함수 단위 테스트.c                 ,    t        dgd      }|dk(  sJ y)u3   AST 스크립트가 없으면 빈 문자열 반환.r-   r   r   Nr   )r8   r:   s     r   %test_returns_empty_when_no_ast_scriptz;TestGetCallersContext.test_returns_empty_when_no_ast_scriptH  s    %|n6HI||r   c                    |dz  }|j                          |dz  j                  d       |dz  }|j                  d       t        j                  ddgdgg dd	d
      }t	               }d|_        ||_        d|_        t        d|      5  t        t        |      gt        |            }ddd       dv sJ d|v sJ y# 1 sw Y   xY w)u5   AST 호출 성공 시 호출 관계 문자열 반환.r   r   r   api.pyzdef get_data():
    pass
r   zserver.py:10rR   )r   r   r   r   r   r   r   r*   r+   Nr   )r   r6   r   r   r   r   r   r   r   r   r5   )r8   r9   r   api_file
ast_outputr   r:   s          r   test_returns_context_on_successz5TestGetCallersContext.test_returns_context_on_successM  s     *	.	.::9E h&9: ZZ ()4 ./"$&'	!


 K	 	%		#)< 	J)3x=/3x=IF	J f$$$'''		J 	Js    C  C	c                    |dz  }|j                          |dz  j                  d       t               }d|_        d|_        d|_        t        d|      5  t        d	gt        |            }d
d
d
       dk(  sJ y
# 1 sw Y   xY w)u+   AST 호출 실패 시 빈 문자열 반환.r   r   r   rM   r   rx   r*   r+   r   N)	r   r6   r   r   r   r   r   r   r5   )r8   r9   r   r   r:   s        r   test_returns_empty_on_ast_errorz5TestGetCallersContext.test_returns_empty_on_ast_errorp  s    *	.	.::9EK	 		"	#)< 	E)8*c(mDF	E ||	E 	Es   A==BN)r?   r@   rA   rB   r   r   r   rC   r   r   r   r   E  s    >
!(Fr   r   c                   "    e Zd ZdZd Zd Zd Zy)TestCodexCascadeuC   3단계 캐스케이드(companion → exec → maat) 동작 검증.c                 "   t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t	        dd      5  t        |g d      }d	d	d	       d	d	d	       d
   dk(  sJ y	# 1 sw Y   xY w# 1 sw Y    xY w)u4   codex-companion 성공 시 source='codex_companion'.r!   rK   r*   r+   codex_gate_check.os.path.isfileTr.   r/   Nrq   r   r4   r=   s        r   5test_companion_success_returns_codex_companion_sourcezFTestCodexCascade.test_companion_success_returns_codex_companion_source  s    9,-	Y"":.&r*	#)< 	8tL )'K`	
 h#4444	 	 	s$   BA9B9B	>BBc                    t        |dz        }t        |      j                  d       t               }d|_        d|_        d|_        t        d|      5  t        |g d	      }d
d
d
       d   dk(  sJ y
# 1 sw Y   xY w)u3   companion 실패 시 마아트 폴백으로 전환.r!   rK   rM   r   zcompanion errorr*   r+   r.   r/   Nrq   rr   r   r8   r9   r0   	fail_procr:   s        r   &test_companion_fail_falls_back_to_maatz7TestCodexCascade.test_companion_fail_falls_back_to_maat  s    9,-	Y"":.K	 		,	#)< 	%#BG\F	 h?222		 	   A77B c                    t        |dz        }t        |      j                  d       t               }d|_        d|_        d|_        t        d|      5  t        |g d	      }d
d
d
       d   dk(  sJ y
# 1 sw Y   xY w)u.   companion, exec 모두 실패 시 maat 폴백.r!   rK   rM   r   rx   r*   r+   r.   r/   Nrq   rr   r   r   s        r   *test_both_codex_fail_returns_maat_fallbackz;TestCodexCascade.test_both_codex_fail_returns_maat_fallback  s    9,-	Y"":.K	 		"	#)< 	%#BG\F	 h?222		 	r  N)r?   r@   rA   rB   r   r  r  rC   r   r   r   r     s    M
53 3r   r   c                   "    e Zd ZdZd Zd Zd Zy)TestMaatFallbackEnhancedu&   마아트 폴백 강화 기능 검증.c                    t        |dz        }t        |      j                  d       g }t        d      D ]9  }|d| dz  }|j                  d|        |j	                  t        |             ; t        ||t        |            }|d   D cg c]  }|d   d	k(  sd
|d   v s| }}t        |      dk\  sJ yc c}w )u7   affected_files 6개 이상이면 high 리스크 경고.r!   r"      filez.pyz# file r	   r%   r#   u   변경 범위r&   rM   N)r5   r   r6   ranger   r   r   )	r8   r9   r0   filesifr:   rP   
high_riskss	            r   test_large_scope_warningz1TestMaatFallbackEnhanced.test_large_scope_warning  s    9,-	Y""#45q 	!AT!C=(ALL71#'LLQ 	!
 &iHF!'tAAjMV4KP_cdercsPsat
t:!### us   B<B<%B<c                     t        |dz        }t        |      j                  d       t        |g t        |            }|d   D cg c]  }|d   dk(  sd|d   v s| }}t	        |      dk\  sJ y	c c}w )
uA   task_file에 보안 민감 키워드 포함 시 medium 리스크.r!   u@   # 인증 모듈 변경
보안 취약점 수정 및 API키 갱신r	   r%   r'   u   보안 민감 키워드r&   rM   Nr5   r   r6   r   r   r8   r9   r0   r:   rP   medium_riskss         r   test_security_keyword_detectionz8TestMaatFallbackEnhanced.test_security_keyword_detection  s    9,-	Y""#fg%iS]C#)'?  Caa
mx6OTmqr  tA  rB  UB  C  C< A%%% Cs   A3A3A3c                     t        |dz        }t        |      j                  d       t        |g t        |            }|d   D cg c]  }|d   dk(  s| }}t	        |      dk(  sJ yc c}w )uH   보안 키워드 없는 일반 task에서는 medium 리스크 미생성.r!   u$   # 로깅 개선
로그 포맷 변경r	   r%   r'   r   Nr  r  s         r   $test_no_false_positive_on_clean_taskz=TestMaatFallbackEnhanced.test_no_false_positive_on_clean_task  sq    9,-	Y""#JK%iS]C#)'?Paa
mx6OPP< A%%% Qs   A+A+N)r?   r@   rA   rB   r  r  r  rC   r   r   r  r    s    0$&&r   r  c                       e Zd ZdZd Zd Zy)TestApiKeyValidationu'   OPENAI_API_KEY 사전 검증 테스트.c                 &   t        |dz        }t        |      j                  d       t        j                  t
        j                  i d      5  t        |g t        |            }ddd       d   dk(  sJ d	|v sJ |d	   J y# 1 sw Y   !xY w)
uW   OPENAI_API_KEY 없어도 companion 호출을 시도하고, 실패 시 마아트 폴백.r!   rK   T)clearr/   Nrq   rr   fallback_reason)r5   r   r6   r   dictr   environr   r8   r9   r0   r:   s       r   6test_no_api_key_still_attempts_companion_then_fallbackzKTestApiKeyValidation.test_no_api_key_still_attempts_companion_then_fallback  s    9,-	Y"":.ZZ

Bd3 	%#!"8}F	 h?222 F***'(444	 	s   BBc                    t        |dz        }t        |      j                  d       t        g       }t	        j
                  t        j                  ddi      5  t	        d|      5  t	        dd      5  t        |g d	
      }ddd       ddd       ddd       d   dk(  sJ y# 1 sw Y   $xY w# 1 sw Y   (xY w# 1 sw Y   ,xY w)uD   OPENAI_API_KEY가 있으면 companion 호출을 시도해야 한다.r!   rK   OPENAI_API_KEYzsk-test-keyr*   r+   r   Tr.   r/   Nrq   r   	r5   r   r6   r   r   r  r   r  r   r=   s        r   #test_api_key_present_attempts_codexz8TestApiKeyValidation.test_api_key_present_attempts_codex  s    9,-	Y"":.&r*	ZZ

%5}$EF 	'i@ <4P -"+')'<F	 h#4444  	 	s<   C (B46B(B4C (B1-B44B=	9C  C	N)r?   r@   rA   rB   r  r#  rC   r   r   r  r    s    15"5r   r  c                       e Zd ZdZd Zy)TestSubprocessEnvPassingu/   subprocess.run에 env=os.environ 전달 검증.c                    t        |dz        }t        |      j                  d       i fd}t        j                  t
        j                  ddi      5  t        d|      5  t        dd	
      5  ddlm}  |dd       ddd       ddd       ddd       dv sJ dd   v sJ y# 1 sw Y   )xY w# 1 sw Y   -xY w# 1 sw Y   1xY w)uE   _run_codex_companion이 subprocess.run에 env를 전달해야 한다.r!   rK   c                      j                  |       t               }d|_        t        j                  g g d      |_        d|_        |S )Nr   r   r   )updater   r   r   r   r   r   )rZ   rm   mockcaptured_kwargss      r   capture_runzJTestSubprocessEnvPassing.test_subprocess_receives_env.<locals>.capture_run  s@    ""6*;DDO**r"%EFDKDKKr   r!  sk-testr*   rp   r   Tr+   r   )_run_codex_companionztest promptr.   Nenv)	r5   r   r6   r   r  r   r  r   r-  )r8   r9   r0   r+  r-  r*  s        @r   test_subprocess_receives_envz5TestSubprocessEnvPassing.test_subprocess_receives_env  s    9,-	Y"":.	 ZZ

%5y$AB 	O'[A O<4P OE(8MNOO	O '''?5#9999O OO O	O 	Os<   C%B73B+B7C+B40B77C 	<CCN)r?   r@   rA   rB   r/  rC   r   r   r%  r%  	  s
    9:r   r%  c                   "    e Zd ZdZd Zd Zd Zy)TestFallbackReasonu   폴백 사유 필드 검증.c           	         t        |dz        }t        |      j                  d       t        j                  t
        j                  ddi      5  t        dt        j                  dd      	      5  t        |g t        |      
      }ddd       ddd       d   dk(  sJ d|v sJ d|d   j                         v s
d|d   v sJ yy# 1 sw Y   AxY w# 1 sw Y   ExY w)uI   companion 타임아웃 시 fallback_reason에 타임아웃 사유 포함.r!   rK   r!  r,  r*   nodex   rd   rp   r/   Nrq   rr   r  rf   u   타임아웃)r5   r   r6   r   r  r   r  rj   rk   r   lowerr  s       r   &test_companion_timeout_includes_reasonz9TestFallbackReason.test_companion_timeout_includes_reason+  s    9,-	Y"":.ZZ

%5y$AB 	'Z5N5NSYcf5gh )'#%#&x=	 h?222 F***F#45;;==SYZkSlAlllAl= 	 	s$   #C2C

C
C	CCc           	         t        |dz        }t        |      j                  d       t               }d|_        d|_        d|_        t        j                  t        j                  ddi      5  t        d|	      5  t        |g t        |      
      }ddd       ddd       d   dk(  sJ d|v sJ y# 1 sw Y   "xY w# 1 sw Y   &xY w)uG   companion 비정상 종료 시 fallback_reason에 에러 사유 포함.r!   rK   rM   r   r   r!  r,  r*   r+   r/   Nrq   rr   r  )r5   r   r6   r   r   r   r   r   r  r   r  r   r   s        r   $test_companion_error_includes_reasonz7TestFallbackReason.test_companion_error_includes_reason<  s    9,-	Y"":.[
 !

5
ZZ

%5y$AB 	'jA )'#%#&x=	 h?222 F*** 	 	s$   .C<B5C5B>	:CC
c                    t        |dz        }t        |      j                  d       t        g       }t	        j
                  t        j                  ddi      5  t	        d|      5  t	        dd      5  t        |g d	
      }ddd       ddd       ddd       d   J y# 1 sw Y   !xY w# 1 sw Y   %xY w# 1 sw Y   )xY w)u)   Codex 성공 시 fallback_reason은 None.r!   rK   r!  r,  r*   r+   r   Tr.   r/   Nr  r"  r=   s        r   %test_codex_success_no_fallback_reasonz8TestFallbackReason.test_codex_success_no_fallback_reasonQ  s    9,-	Y"":.&r*	ZZ

%5y$AB 	'i@ <4P -"+')'<F	 '(000  	 	s<   B=(B16B%B1B=%B.*B11B:	6B==CN)r?   r@   rA   rB   r6  r8  r:  rC   r   r   r1  r1  (  s    &m"+*1r   r1  c                   "    e Zd ZdZd Zd Zd Zy)TestSanitizeGateIntegrationu4   Codex 호출 전 PII 마스킹 자동 적용 검증.c           	      h   t        |dz        }t        |      j                  d       g fd}t        d|      5  t        dd      5  t	        |g t        |      	      }d
d
d
       d
d
d
       d   du sJ rd   }d|vsJ d       d|vsJ d       y
y
# 1 sw Y   9xY w# 1 sw Y   =xY w)uP   프롬프트에 PII(전화번호)가 포함되면 마스킹 후 Codex에 전달.r!   u>   # 설계
연락처: 010-1234-5678
주민번호: 900101-1234567c                     | r| d   n|j                  dg       }t        |      }d|v r|j                  d      }|dz   t        |      k  r]||dz      }t        j
                  j                  |      r6t        |dd      5 }j                  |j                                d d d        t               }d|_        t        j                  g g d      |_        d	|_        |S # 1 sw Y   @xY w)
Nr   rZ   r   rM   rP   r   r   r   r   )rh   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )	rZ   rm   re   r   r   r   r   r)  captured_promptss	           r   r+  z_TestSanitizeGateIntegration.test_pii_in_prompt_is_masked_before_codex_call.<locals>.capture_runq  s    !$q'vzz&"'=CCyH(*nn_57S]*"*37"3Kww~~k2!+sWE ?,33BGGI>?;DDO**r"%EFDKDKK? ?s    C  C)r*   rp   r   Tr+   r/   Nr3   r   z010-1234-5678u)   전화번호가 마스킹되지 않았음z900101-1234567u)   주민번호가 마스킹되지 않았음)r5   r   r6   r   r   )r8   r9   r0   r+  r:   prompt_textr?  s         @r   .test_pii_in_prompt_is_masked_before_codex_callzJTestSanitizeGateIntegration.test_pii_in_prompt_is_masked_before_codex_callj  s    9,-	Y""#ef	  #= 	8tL )'#%#&x=	 f~%%%*1-K"+5b7bb5#;6c8cc6  	 	s#   B(B$B(B%	!B((B1c                 v   t        |dz        }t        |      j                  d       t        g       }t	        dd      5  t	        d|      5  t	        dd      5  t        |g d	
      }ddd       ddd       ddd       d   du sJ |d   dk(  sJ y# 1 sw Y   -xY w# 1 sw Y   1xY w# 1 sw Y   5xY w)uD   sanitize_gate import 실패해도 codex_gate_check는 정상 동작.r!   u!   # 설계
연락처: 010-1234-5678z$codex_gate_check._SANITIZE_AVAILABLEFr*   r+   r   Tr.   r/   Nr3   rq   r   r4   r=   s        r   %test_sanitize_unavailable_still_workszATestSanitizeGateIntegration.test_sanitize_unavailable_still_works  s    9,-	Y""#GH&r*	95A 	'i@ <4P -"+')'<F	 f~%%%h#4444  	 	s<    B/B#B+B#3B/B B##B,	(B//B8c                     t        |dz        }t        |      j                  d       t        g       }t	        d|      5  t	        dd      5  t        |g d      }d	d	d	       d	d	d	       d
   du sJ y	# 1 sw Y   xY w# 1 sw Y   xY w)u7   PII 없는 프롬프트에서는 마스킹 감지 0건.r!   u+   # 일반 설계 문서
로깅 개선 작업r*   r+   r   Tr.   r/   Nr3   r4   r=   s        r   test_no_pii_no_masking_logz6TestSanitizeGateIntegration.test_no_pii_no_masking_log  s    9,-	Y""#QR&r*	#)< 	8tL )'#%#8	 f~%%% 	 	s$   BA8B8B	=BBN)r?   r@   rA   rB   rA  rC  rE  rC   r   r   r<  r<  g  s    >$dL5&&r   r<  c                   "    e Zd ZdZd Zd Zd Zy)TestGateFileAutoGenerationud   codex_gate_check()가 task_id 전달 시 .codex-gate 결과 파일을 자동 생성하는지 검증.c           	         t        |dz        }t        |      j                  d       |dz  dz  }|j                  d       t	        g       }t        d|      5  t        d	d      5  t        |g t        |      d
      }ddd       ddd       |dz  }|j                         sJ d       t        j                  |j                               }|d   d
k(  sJ d|v sJ |d   d   k(  sJ y# 1 sw Y   nxY w# 1 sw Y   rxY w)u9   Codex 성공 시 결과 파일이 생성되어야 한다.r!   rK   memoryeventsTparentsr*   r+   r   ztask-test-001r0   r1   r2   task_idNztask-test-001.codex-gateu$   결과 파일이 생성되어야 함rN  	timestampr3   )r5   r   r6   r   r   r   r   existsr   loads	read_text)r8   r9   r0   
events_dirr   r:   	gate_filedatas           r   'test_gate_file_created_on_codex_successzBTestGateFileAutoGeneration.test_gate_file_created_on_codex_success  s   9,-	Y"":.(83
&&r*	#)< 	8tL )'#%#&x=+		 !;;	!I#II!zz)--/0I/111d"""F|vf~--- 	 	s$   C;)C/C;/C8	4C;;Dc                    t        |dz        }t        |      j                  d       |dz  dz  }|j                  d       t	               }d|_        d|_        d	|_        t        d
|      5  t        |g t        |      d       ddd       |dz  }|j                         sJ d       t        j                  |j                               }|d   dk(  sJ y# 1 sw Y   SxY w)uC   마아트 폴백 시에도 결과 파일이 생성되어야 한다.r!   rK   rI  rJ  TrK  rM   r   rx   r*   r+   ztask-test-002rM  Nztask-test-002.codex-gateu5   폴백 시에도 결과 파일이 생성되어야 함rq   rr   )r5   r   r6   r   r   r   r   r   r   r   rP  r   rQ  rR  )r8   r9   r0   rS  r   rT  rU  s          r   'test_gate_file_created_on_maat_fallbackzBTestGateFileAutoGeneration.test_gate_file_created_on_maat_fallback  s    9,-	Y"":.(83
&[
 !

#
#*= 	#!"8}'		 !;;	!Z#ZZ!zz)--/0H~000	 	s   /CC#c                 n   t        |dz        }t        |      j                  d       |dz  dz  }|j                  d       t	        g       }t        d|      5  t        |g t        |      	       d
d
d
       t        |j                  d            }t        |      dk(  sJ d       y
# 1 sw Y   9xY w)uA   task_id 없으면 결과 파일이 생성되지 않아야 한다.r!   rK   rI  rJ  TrK  r*   r+   r/   Nz*.codex-gater   u)   task_id 없으면 결과 파일 미생성)
r5   r   r6   r   r   r   r   r   globr   )r8   r9   r0   rS  r   
gate_filess         r   !test_no_gate_file_without_task_idz<TestGateFileAutoGeneration.test_no_gate_file_without_task_id  s    9,-	Y"":.(83
&&r*	#)< 	#!"8}	 *//.9:
:!#P%PP#	 	s   B++B4N)r?   r@   rA   rB   rV  rX  r\  rC   r   r   rG  rG    s    n.012Qr   rG  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestNormalizeAffectedItemu8   _normalize_affected_item 헬퍼 함수 단위 테스트.c                 :    t        d      \  }}|dk(  sJ |du sJ y)u*   문자열 입력 시 (path, False) 반환.file.pyFNr   r8   r   is_news      r   "test_string_returns_path_and_falsez<TestNormalizeAffectedItem.test_string_returns_path_and_false  s*    /	:fy   r   c                 @    t        ddd      \  }}|dk(  sJ |du sJ y)u2   dict + is_new=True 입력 시 (path, True) 반환.znew_file.pyTr   rc  Nra  rb  s      r   test_dict_with_is_new_truez4TestNormalizeAffectedItem.test_dict_with_is_new_true  s0    /RV0WXf}$$$~~r   c                 @    t        ddd      \  }}|dk(  sJ |du sJ y)u4   dict + is_new=False 입력 시 (path, False) 반환.zexisting.pyFrf  Nra  rb  s      r   test_dict_with_is_new_falsez5TestNormalizeAffectedItem.test_dict_with_is_new_false  s0    /RW0XYf}$$$r   c                 >    t        ddi      \  }}|dk(  sJ |du sJ y)u-   dict에 is_new 키 없으면 False 기본값.r   r`  FNra  rb  s      r   'test_dict_without_is_new_defaults_falsezATestNormalizeAffectedItem.test_dict_without_is_new_defaults_false  s/    /0CDfy   r   N)r?   r@   rA   rB   rd  rg  ri  rk  rC   r   r   r^  r^    s    Br   r^  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestIsNewMaatFallbackuC   is_new 메타데이터에 따른 마아트 폴백 동작 테스트.c                    t        |dz        }t        |      j                  d       t        |dddgt        |            }|d   du sJ |d   D cg c]  }|d   d	k(  s| }}t	        |      d
k(  sJ d|d   d   v sJ yc c}w )u1   is_new=True인 미존재 파일은 info severity.r!   rK   zbrand_new.pyTrf  r3   r	   r%   inforM   u   신규 파일r   r&   Nr  )r8   r9   r0   r:   rP   
info_riskss         r   test_new_file_missing_is_infoz3TestIsNewMaatFallback.test_new_file_missing_is_info#  s    9,-	Y"":.%$56M

 f~%%%!'LAAjMV4KaL
L:!###*Q-">>>> M   B!Bc                    t        |dz        }t        |      j                  d       t        |dddgt        |            }|d   du sJ |d   D cg c]  }|d	   d
k(  s| }}t	        |      dk(  sJ d|d   d   v sJ yc c}w )u2   is_new=False인 미존재 파일은 high severity.r!   rK   zshould_exist.pyFrf  r3   Tr	   r%   r#   rM   u   오타 또는 삭제됨r   r&   Nr  r8   r9   r0   r:   rP   r  s         r   "test_existing_file_missing_is_highz8TestIsNewMaatFallback.test_existing_file_missing_is_high2  s    9,-	Y"":.%'59:M

 f~%%%!'LAAjMV4KaL
L:!###(JqM-,HHHH Mrr  c                     t        |dz        }t        |      j                  d       t        |dgt        |            }|d   du sJ |d   D cg c]  }|d   dk(  s| }}t	        |      d	k(  sJ y
c c}w )uE   문자열 형식(기존 호환)의 미존재 파일은 high severity.r!   rK   zmissing_file.pyr3   Tr	   r%   r#   rM   Nr  rt  s         r    test_string_missing_file_is_highz6TestIsNewMaatFallback.test_string_missing_file_is_highA  s    9,-	Y"":.%M

 f~%%%!'LAAjMV4KaL
L:!### Ms   A5A5c           	         t        |dz        }t        |      j                  d       |dz  }|j                  d       t        |t        |      ddddd	d
dgt        |            }|d   du sJ |d   D cg c]  }|d   dk(  s| }}|d   D cg c]  }|d   dk(  s| }}t	        |      dk(  sJ t	        |      dk(  sJ yc c}w c c}w )u&   문자열 + dict 혼합 형식 지원.r!   rK   z	exists.pyz# okznot_here.pyznew_module.pyTrf  z
deleted.pyFr3   r	   r%   r#   ro  rR   rM   Nr  )r8   r9   r0   existingr:   rP   r  rp  s           r    test_mixed_format_affected_filesz6TestIsNewMaatFallback.test_mixed_format_affected_filesO  s    9,-	Y"":.k)F#%H(D9%7	 M	
 f~%%%!'LAAjMV4KaL
L!'LAAjMV4KaL
L:!###:!### MLs   8CCC!Cc                 >   t        |dz        }t        |      j                  d       |dz  }|j                  d       t        |t        |      ddgt        |            }|d   D cg c]  }d|j	                  dd	      v s| }}t        |      d
k(  sJ yc c}w )uD   실제 존재하는 파일은 is_new 여부 무관 리스크 없음.r!   rK   zreal.pyz# real fileTrf  r	   r&   r   r   N)r5   r   r6   r   rh   r   )r8   r9   r0   ry  r:   rP   
file_riskss          r   test_existing_file_no_riskz0TestIsNewMaatFallback.test_existing_file_no_riskg  s    9,-	Y"":.i'M*%(mt45M
 "(ZAI}VXAY4YaZ
Z:!### [s   )BBN)	r?   r@   rA   rB   rq  ru  rw  rz  r}  rC   r   r   rm  rm     s    M?I$$0$r   rm  r   )'rB   r   r   rj   syspathlibr   unittest.mockr   r   r   insertr5   __file__parentr   r   r   r   r   r   r   rE   rW   r~   r   r   r   r   r   r   r  r  r%  r1  r<  rG  r^  rm  rC   r   r   <module>r     sV    	  
  * 3tH~,,334 5 ] ]
d 
 
	 

T 
t 
y 
 %& %&V*# *#`05 05l>' >'HMq MqfG/ G/ZQ" Q"n 29 9~-3 -3f"& "&P$5 $5T: :>91 91~K& K&bDQ DQX 8U$ U$r   