
    i}o                       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
e
j                  j                  de	j                  j                  e	j                  j                  e      d             ddlmZmZ ddlZddlmZmZmZ ddlmZ dddZddd	Zddd
Z G d d      Z G d d      Z G d d      Z G d d      Z y)u  tests/test_bot_api.py — engine_v2/bot_api.py TDD RED 단계 테스트 스위트.

bot_api.py는 아직 존재하지 않으므로, 이 파일을 실행하면 ImportError로 실패한다.
구현 후 GREEN 단계에서 모든 테스트가 통과해야 한다.

패치 전략:
  - CLIRunner.run_xxx 메서드를 AsyncMock으로 패치하여 CLIResult를 직접 반환한다.
  - subprocess 레벨이 아닌 CLIRunner 레벨에서 모킹하므로 bot_api.py 내부 로직만 검증한다.
    )annotationsNz..)	AsyncMockpatch)call_claude
call_codexcall_gemini	CLIResultc                "    t        ||d| d      S )u1   returncode=0인 정상 CLIResult를 생성한다.r   Fstdoutstderr
returncodeengine	timed_outr	   )r   r   r   s      A/home/jay/workspace/services/multimodel-bot/tests/test_bot_api.py_okr      s         c                "    t        |||| d      S )u-   returncode != 0인 CLIResult를 생성한다.Fr   r	   )r   r   r   r   s       r   _errr   (   s     r   c                *    t        dd| dd| d      S )u,   timed_out=True인 CLIResult를 생성한다. zTimeout after sTr   r	   )r   timeout_secs     r   _timeoutr   3   s'    }A. r   c                     e Zd ZdZej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Z	ej
                  j                  dd       Z
ej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd	       Zej
                  j                  dd
       Zej
                  j                  dd       Zej
                  j                  dd       Zy)TestCallClaudeu<   call_claude() — CLIRunner.run_claude 래퍼 동작 검증.c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)D   정상 응답 시 CLIResult.stdout을 그대로 반환해야 한다.claudeu!   Claude의 정상 응답입니다.&engine_v2.bot_api.CLIRunner.run_claudereturn_valuenew   테스트 프롬프트N==z%(py0)s == %(py3)sresponsepy0py3assert %(py5)spy5r   r   r   r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationselfresultr+   @py_assert2@py_assert1@py_format4@py_format6s          r   #test_normal_response_returns_stdoutz2TestCallClaude.test_normal_response_returns_stdoutH   s      XBC;X^A_` 	C()ABBH	C ?>x>>>>>x>>>>>>>x>>>x>>>>>>>>>>> C	C 	Cs,   $C7C+C)C+B/C7)C++C40C7c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)o   timed_out=True인 CLIResult가 반환되면 '⏱ 응답 시간 초과 (N초)' 메시지를 반환해야 한다.r!   X  r   r"   r#   r%      타임아웃 테스트timeoutN!   ⏱ 응답 시간 초과 (600초)r(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   $test_timeout_returns_timeout_messagez3TestCallClaude.test_timeout_returns_timeout_messageT         (4;X^A_` 	P()A3OOH	P ?>x>>>>>x>>>>>>>x>>>x>>>>>>>>>>> P	P 	P,   %C:C.C,C.B/C:,C..C73C:c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)M   timeout 파라미터 값이 타임아웃 메시지에 반영되어야 한다.r!      rE   r"   r#   r%      커스텀 타임아웃rG   Nu    ⏱ 응답 시간 초과 (30초)r(   r*   r+   r,   r/   r0   rJ   r:   s          r   ,test_timeout_message_reflects_custom_timeoutz;TestCallClaude.test_timeout_message_reflects_custom_timeout^         (3;X^A_` 	O()A2NNH	O >=x=====x=======x===x=========== O	O 	OrM   c                  K   d}t        dd|d      }t        dt        |            5  t        d	       d
{   }d
d
d
       d}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}d}||v}|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}d}||v}|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}y
7 # 1 sw Y   xY ww)ur   returncode != 0이고 stdout이 없을 때 stderr에서 'error' 포함 줄만 필터링하여 반환해야 한다.z8info: starting
Error: authentication failed
debug: done
r!   r      r   r   r   r"   r#   r%      에러 필터링 테스트NzError: authentication failedinz%(py1)s in %(py3)sr+   py1r.   r/   r0   zinfo: startingnot inz%(py1)s not in %(py3)szdebug: doneu   ❌ Claude CLI 에러 (exit 1):r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r;   r   r<   r+   @py_assert0r=   r?   r@   s           r   5test_nonzero_returncode_no_stdout_filters_error_lineszDTestCallClaude.test_nonzero_returncode_no_stdout_filters_error_linesj   s     Ohr&QG;X^A_` 	G()EFFH	G .9-9999-999-9999999999999999/x////x/////////x///x///////,}H,,,,}H,,,},,,,,,H,,,H,,,,,,,0<0H<<<<0H<<<0<<<<<<H<<<H<<<<<<< G	G 	Gs,   )K,KKKJK,KK)$K,c                H  K   d}t        dd|d      }t        dt        |            5  t        d	       d
{   }d
d
d
       d}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d
x}}y
7 X# 1 sw Y   XxY ww)ux   returncode != 0이고 stdout이 없고 stderr에 'error' 줄도 없으면 '상세 내용 없음'을 반환해야 한다.z%warning: something odd
info: exiting
r!   r      rV   r"   r#   r%      error 줄 없음 테스트Nu   ❌ Claude CLI 에러 (exit 2):rX   rZ   r+   r[   r/   r0      상세 내용 없음r`   ra   s           r   @test_nonzero_returncode_no_stdout_no_error_lines_returns_genericzOTestCallClaude.test_nonzero_returncode_no_stdout_no_error_lines_returns_genericx   s     ;hr&QG;X^A_` 	G()EFFH	G 1<0H<<<<0H<<<0<<<<<<H<<<H<<<<<<<%1%1111%111%1111111111111111 G	G 	Gs,   )F"FFFEF"FFF"c                  K   t        dddd      }t        dt        |            5  t        d	       d
{   }d
d
d
       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
7 # 1 sw Y   xY ww)uo   returncode != 0이더라도 stdout에 출력이 있으면 에러를 무시하고 stdout을 반환해야 한다.r!   zpartial output from claudezError: some stderrrU   rV   r"   r#   r%   u   출력 있는 에러 테스트Nr(   r*   r+   r,   r/   r0   )r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   2test_nonzero_returncode_with_stdout_returns_stdoutzATestCallClaude.test_nonzero_returncode_with_stdout_returns_stdout   s      h'CL`mno;X^A_` 	K()IJJH	K 87x77777x7777777x777x77777777777 K	K 	Ks,   'C:C.C,C.B/C:,C..C73C:c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)Y   returncode=0이고 stdout이 빈 문자열이면 경고 메시지를 반환해야 한다.r!   r   r   r"   r#   r%      빈 응답 테스트Nu<   ⚠️ Claude CLI에서 빈 응답이 반환되었습니다.r(   r*   r+   r,   r/   r0   r1   r:   s          r   0test_empty_stdout_returns_empty_response_messagez?TestCallClaude.test_empty_stdout_returns_empty_response_message   s      Xb);X^A_` 	A()?@@H	A ZYxYYYYYxYYYYYYYxYYYxYYYYYYYYYYY A	A 	A,   %C8C,C*C,B/C8*C,,C51C8c                  K   t        dd      }t        |      }t        d|      5  t        d       d{    ddd       |j                  \  }}|j
                  }d	} ||      }d
}||u }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}	}y7 # 1 sw Y   xY ww)u[   code_analysis=False(기본값)이 CLIRunner.run_claude에 그대로 전달되어야 한다.r!      응답rm   r#   r"   r%   u   일반 질문Ncode_analysisFiszI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} is %(py9)skwargsr-   py2py4py6py9assert %(py11)spy11r   r   r   r   	call_argsgetr2   r3   r4   r5   r6   r7   r8   r9   r;   r<   mock_run_rw   r>   @py_assert3@py_assert5@py_assert8@py_assert7@py_format10@py_format12s               r   )test_code_analysis_false_passed_to_runnerz8TestCallClaude.test_code_analysis_false_passed_to_runner   s      Xh/&1;J 	/o...	/ &&	6zz3/3z/*3e3*e3333*e333333v333v333z333/333*333e33333333 /	/ 	/s,   'E"EEEDE"EEE"c                  K   t        dd      }t        |      }t        d|      5  t        dd	       d
{    d
d
d
       |j                  \  }}|j
                  }d} ||      }d}||u }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d
x}x}x}x}	}y
7 # 1 sw Y   xY ww)uO   code_analysis=True가 CLIRunner.run_claude에 그대로 전달되어야 한다.r!   u   코드 분석 결과rm   r#   r"   r%   u   코드 분석 요청T)rs   Nrs   rt   rv   rw   rx   r}   r~   r   r   s               r   (test_code_analysis_true_passed_to_runnerz7TestCallClaude.test_code_analysis_true_passed_to_runner   s     X&<=&1;J 	J4DIII	J &&	6zz2/2z/*2d2*d2222*d222222v222v222z222/222*222d22222222 J	J 	Js,   'E$EEEDE$EE!E$c                  K   t        dd      }t        |      }t        d|      5  t        dd	       d
{    d
d
d
       |j                  \  }}|j
                  }d} ||      }d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d
x}x}x}x}	}y
7 # 1 sw Y   xY ww)uG   timeout 파라미터가 CLIRunner.run_claude에 전달되어야 한다.r!   rr   rm   r#   r"   r%   u   타임아웃 값 확인x   rG   NrH   r(   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)srw   rx   r}   r~   r   r   s               r   #test_timeout_param_passed_to_runnerz2TestCallClaude.test_timeout_param_passed_to_runner   s     Xh/&1;J 	F7EEE	F &&	6zz+)+z)$++$++++$++++++v+++v+++z+++)+++$+++++++++++ F	F 	F,   'E%EEEDE%EE"E%c                   K   t        dd      }t        |      }d}t        d|      5  t        |       d{    ddd       |j                  \  }}|d	   }||k(  }|st        j                  d
|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}y7 # 1 sw Y   xY ww)u^   프롬프트 문자열이 CLIRunner.run_claude에 첫 번째 인자로 전달되어야 한다.r!   rr   rm   r#   *   이 프롬프트가 전달되어야 한다r"   r%   Nr   r(   z%(py1)s == %(py3)spromptr[   r/   r0   )r   r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   
r;   r<   r   r   argsr   rb   r=   r?   r@   s
             r   test_prompt_passed_to_runnerz+TestCallClaude.test_prompt_passed_to_runner   s      Xh/&1=;J 	&f%%%	& $$aAw w&    w&   w      &   &        &	& 	&,   )DDD DCD DDDNreturnNone)__name__
__module____qualname____doc__pytestmarkasynciorA   rK   rR   rc   rh   rj   ro   r   r   r   r    r   r   r   r   C   sQ   F [[? ? [[? ? [[> > [[= = [[	2 	2 [[8 8 [[Z Z [[	4 	4 [[	3 	3 [[	, 	, [[
! 
!r   r   c                  v   e Zd ZdZej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Z	ej
                  j                  dd       Z
ej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd	       Zej
                  j                  dd
       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zy)TestCallCodexu:   call_codex() — CLIRunner.run_codex 래퍼 동작 검증.c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)r    codexu    Codex의 정상 응답입니다.rm   %engine_v2.bot_api.CLIRunner.run_codexr#   r%   u   코드 리뷰 요청Nr(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   rA   z1TestCallCodex.test_normal_response_returns_stdout   s      W%GH:	W]@^_ 	@'(>??H	@ >=x=====x=======x===x=========== @	@ 	@rp   c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)rC   r   rD   rE   r   r#   r%   rF   rG   NrI   r(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   rK   z2TestCallCodex.test_timeout_returns_timeout_message   s      's3:	W]@^_ 	O'(@#NNH	O ?>x>>>>>x>>>>>>>x>>>x>>>>>>>>>>> O	O 	OrM   c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)rO   r   -   rE   r   r#   r%   rQ   rG   Nu    ⏱ 응답 시간 초과 (45초)r(   r*   r+   r,   r/   r0   r   r:   s          r   rR   z:TestCallCodex.test_timeout_message_reflects_custom_timeout   s      'r2:	W]@^_ 	N'(@"MMH	N >=x=====x=======x===x=========== N	N 	NrM   c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)uj   returncode != 0이고 stderr에 'login'이 포함되면 로그인 안내 메시지를 반환해야 한다.r   z3Error: Not authenticated. Please run `codex login`.rU   r   r   r   r#   r%   u   로그인 없이 요청NM   🔑 Codex 로그인이 필요합니다. `codex login`을 실행해주세요.r(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   <test_nonzero_returncode_login_in_stderr_returns_auth_messagezJTestCallCodex.test_nonzero_returncode_login_in_stderr_returns_auth_message   s      g&[hij:	W]@^_ 	C'(ABBH	C kjxjjjjjxjjjjjjjxjjjxjjjjjjjjjjj C	C 	C,   &C9C-C+C-B/C9+C--C62C9c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)ui   returncode != 0이고 stderr에 'auth'가 포함되면 로그인 안내 메시지를 반환해야 한다.r   zauthentication requiredrU   r   r   r#   r%   u   auth 에러 테스트Nr   r(   r*   r+   r,   r/   r0   r   r:   s          r   ;test_nonzero_returncode_auth_in_stderr_returns_auth_messagezITestCallCodex.test_nonzero_returncode_auth_in_stderr_returns_auth_message  s      g&?AN:	W]@^_ 	A'(?@@H	A kjxjjjjjxjjjjjjjxjjjxjjjjjjjjjjj A	A 	Ar   c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)uw   returncode != 0이고 stderr에 'usage limit'이 포함되면 사용량 한도 초과 메시지를 반환해야 한다.r   z)Error: You have reached your usage limit.rU   r   r   r#   r%   u   사용량 한도 테스트NK   ⚠️ Codex 사용량 한도 초과. 잠시 후 다시 시도해주세요.r(   r*   r+   r,   r/   r0   r   r:   s          r   Ctest_nonzero_returncode_usage_limit_in_stderr_returns_limit_messagezQTestCallCodex.test_nonzero_returncode_usage_limit_in_stderr_returns_limit_message  s      g&Q^_`:	W]@^_ 	F'(DEEH	F ihxhhhhhxhhhhhhhxhhhxhhhhhhhhhhh F	F 	Fr   c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)ut   returncode != 0이고 stderr에 'hit your'가 포함되면 사용량 한도 초과 메시지를 반환해야 한다.r   z You have hit your monthly quota.rU   r   r   r#   r%   u   hit your 에러 테스트Nr   r(   r*   r+   r,   r/   r0   r   r:   s          r   @test_nonzero_returncode_hit_your_in_stderr_returns_limit_messagezNTestCallCodex.test_nonzero_returncode_hit_your_in_stderr_returns_limit_message  s      g&HUVW:	W]@^_ 	E'(CDDH	E ihxhhhhhxhhhhhhhxhhhxhhhhhhhhhhh E	E 	Er   c                  K   d}t        d|d      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}d}||v}|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}d}||v}|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}y	7 # 1 sw Y   xY ww)ud   returncode != 0이고 stderr에 'error' 줄이 있으면 필터링된 줄만 반환되어야 한다.zAinfo: starting process
Error: connection refused
debug: retrying
r   rU   r   r   r#   r%   rW   NzError: connection refusedrX   rZ   r+   r[   r/   r0      ❌ Codex CLI 에러 (exit 1):zinfo: starting processr]   r_   zdebug: retryingr   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   ra   s           r   9test_nonzero_returncode_error_lines_filtered_and_returnedzGTestCallCodex.test_nonzero_returncode_error_lines_filtered_and_returned&  s     Xgf;:	W]@^_ 	F'(DEEH	F +6*h6666*h666*666666h666h6666666/;/8;;;;/8;;;/;;;;;;8;;;8;;;;;;;'7'x7777'x777'777777x777x7777777 0 0000 000 0000000000000000 F	F 	Fs,   (K+KKKJK+KK(#K+c                F  K   d}t        d|d      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}y	7 X# 1 sw Y   XxY ww)ug   returncode != 0이고 stderr에 'error' 줄이 없으면 '상세 내용 없음'을 반환해야 한다.z1warning: something happened
info: process exited
r   rU   r   r   r#   r%   rf   Nr   rX   rZ   r+   r[   r/   r0   rg   r   ra   s           r   >test_nonzero_returncode_no_error_lines_returns_generic_messagezLTestCallCodex.test_nonzero_returncode_no_error_lines_returns_generic_message4  s     Ggf;:	W]@^_ 	F'(DEEH	F 0;/8;;;;/8;;;/;;;;;;8;;;8;;;;;;;%1%1111%111%1111111111111111 F	F 	Fs,   (F!FFFEF!FFF!c                  K   d}t        d|d      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}t	        |      d	 }t	        |      }d}||k  }|st        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                  |      dz  }	dd|	iz  }
t        t        j                  |
            d	x}x}}y	7 .# 1 sw Y   .xY ww)uF   필터링된 에러 내용이 300자를 초과하면 잘려야 한다.a  Error: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxr   rU   r   r   r#   r%   u   300자 제한 테스트Nu   ❌ Codex CLI 에러 (exit 1): i,  )<=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)slen
error_part)r-   r\   r.   r{   zassert %(py8)spy8)r   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   )r;   
long_errorr<   r+   prefixr   r=   r   @py_assert4@py_format7@py_format9s              r   )test_error_filtered_content_max_300_charsz7TestCallCodex.test_error_filtered_content_max_300_chars@  s      +
gjQ?:	W]@^_ 	C'(ABBH	C 3c&km,
:%#%#%%%%#%%%%%%s%%%s%%%%%%:%%%:%%%%%%#%%%%%%%	 C	C 	Cs,   (E7E*E'E*D)E7'E**E4/E7c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)rl   r   r   rm   r   r#   r%   rn   Nu;   ⚠️ Codex CLI에서 빈 응답이 반환되었습니다.r(   r*   r+   r,   r/   r0   r   r:   s          r   ro   z>TestCallCodex.test_empty_stdout_returns_empty_response_messageO  s      WR(:	W]@^_ 	@'(>??H	@ YXxXXXXXxXXXXXXXxXXXxXXXXXXXXXXX @	@ 	@rp   c                  K   t        dd      }t        |      }t        d|      5  t        d       d{    ddd       |j                  \  }}|j
                  }d	} ||      }d
}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}	}y7 # 1 sw Y   xY ww)um   model 파라미터 없이 호출하면 기본값 'gpt-5.1-codex-mini'가 CLIRunner에 전달되어야 한다.r   okrm   r#   r   r%   u   기본 모델 테스트Nmodelzgpt-5.1-codex-minir(   r   rw   rx   r}   r~   r   r   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r   s               r   #test_default_model_passed_to_runnerz1TestCallCodex.test_default_model_passed_to_runner[  s      WT*&1:I 	86777	8 &&	6zz:':z'":&::"&:::::"&:::::::v:::v:::z:::':::":::&::::::::: 8	8 	8s,   'E#EEEDE#EE E#c                  K   t        dd      }t        |      }t        d|      5  t        dd	       d
{    d
d
d
       |j                  \  }}|j
                  }d} ||      }d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d
x}x}x}x}	}y
7 # 1 sw Y   xY ww)uN   model 파라미터가 CLIRunner.run_codex에 그대로 전달되어야 한다.r   r   rm   r#   r   r%   u   커스텀 모델 테스트zgpt-5.2-codex)r   Nr   r(   r   rw   rx   r}   r~   r   r   s               r   "test_custom_model_passed_to_runnerz0TestCallCodex.test_custom_model_passed_to_runnerg  s     WT*&1:I 	R9QQQ	R &&	6zz5'5z'"5o5"o5555"o555555v555v555z555'555"555o55555555 R	R 	Rr   c                  K   t        dd      }t        |      }t        d|      5  t        dd	       d
{    d
d
d
       |j                  \  }}|j
                  }d} ||      }d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d
x}x}x}x}	}y
7 # 1 sw Y   xY ww)uF   timeout 파라미터가 CLIRunner.run_codex에 전달되어야 한다.r   r   rm   r#   r   r%      타임아웃 전달 테스트Z   rG   NrH   r(   r   rw   rx   r}   r~   r   r   s               r   r   z1TestCallCodex.test_timeout_param_passed_to_runners  s     WT*&1:I 	J<bIII	J &&	6zz*)*z)$**$****$******v***v***z***)***$*********** J	J 	Jr   c                   K   t        dd      }t        |      }d}t        d|      5  t        |       d{    ddd       |j                  \  }}|d	   }||k(  }|st        j                  d
|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}y7 # 1 sw Y   xY ww)u]   프롬프트 문자열이 CLIRunner.run_codex에 첫 번째 인자로 전달되어야 한다.r   r   rm   r#   r   r   r%   Nr   r(   r   r   r[   r/   r0   )r   r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r   s
             r   r   z*TestCallCodex.test_prompt_passed_to_runner  s      WT*&1=:I 	%V$$$	% $$aAw w&    w&   w      &   &        %	% 	%r   Nr   )r   r   r   r   r   r   r   rA   rK   rR   r   r   r   r   r   r   r   ro   r   r   r   r   r   r   r   r   r      s   D [[> > [[? ? [[> > [[k k [[k k [[i i [[i i [[1 1 [[	2 	2 [[
& 
& [[Y Y [[	; 	; [[	6 	6 [[	+ 	+ [[
! 
!r   r   c                  T   e Zd ZdZej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Z	ej
                  j                  dd       Z
ej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd	       Zej
                  j                  dd
       Zej
                  j                  dd       Zy)TestCallGeminiu<   call_gemini() — CLIRunner.run_gemini 래퍼 동작 검증.c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)r    geminiu!   Gemini의 정상 응답입니다.rm   &engine_v2.bot_api.CLIRunner.run_geminir#   r%   r'   Nr(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   rA   z2TestCallGemini.test_normal_response_returns_stdout  s      X&IJ;X^A_` 	C()ABBH	C ?>x>>>>>x>>>>>>>x>>>x>>>>>>>>>>> C	C 	Crp   c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)rC   r   rD   rE   r   r#   r%   rF   rG   NrI   r(   r*   r+   r,   r/   r0   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r:   s          r   rK   z3TestCallGemini.test_timeout_returns_timeout_message  rL   rM   c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)rO   r   <   rE   r   r#   r%   rQ   rG   Nu    ⏱ 응답 시간 초과 (60초)r(   r*   r+   r,   r/   r0   r   r:   s          r   rR   z;TestCallGemini.test_timeout_message_reflects_custom_timeout  rS   rM   c                  K   d}t        d|d      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}||v }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            d	}y	7 # 1 sw Y   xY ww)uY   returncode != 0이면 stderr 내용을 포함한 에러 메시지를 반환해야 한다.z*gemini: command failed: API quota exceededr   rU   r   r   r#   r%   u   Gemini 에러 테스트Nu   ❌ Gemini CLI 에러 (exit 1):rX   rZ   r+   r[   r/   r0   z%(py0)s in %(py2)sr   r-   ry   assert %(py4)srz   r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   )r;   r   r<   r+   rb   r=   r?   r@   r>   @py_format3@py_format5s              r   1test_nonzero_returncode_returns_error_with_stderrz@TestCallGemini.test_nonzero_returncode_returns_error_with_stderr  s     >hv!<;X^A_` 	D()BCCH	D 1<0H<<<<0H<<<0<<<<<<H<<<H<<<<<<<!!!!v!!!!!!v!!!v!!!!!!!!!!!!!!!! D	D 	Ds,   (G
F=F:F=E<G
:F==GG
c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}y	7 # 1 sw Y   xY ww)u?   에러 메시지에 실제 exit code가 포함되어야 한다.r   z
some errorre   r   r   r#   r%   u   exit code 확인Nzexit 2rX   rZ   r+   r[   r/   r0   r   r;   r<   r+   rb   r=   r?   r@   s          r   ,test_nonzero_returncode_exit_code_in_messagez;TestCallGemini.test_nonzero_returncode_exit_code_in_message  s      h|B;X^A_` 	=();<<H	= #x8####x8###x######8###8####### =	= 	=,   &C8C,C*C,B.C8*C,,C51C8c                  K   t        ddd      }t        dt        |            5  t        d       d	{   }d	d	d	       d
}|v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	x}}y	7 # 1 sw Y   xY ww)u`   returncode != 0이고 stderr가 비어 있어도 에러 메시지 형식을 반환해야 한다.r   r      r   r   r#   r%   u   빈 stderr 에러Nu   ❌ Gemini CLI 에러 (exit 3):rX   rZ   r+   r[   r/   r0   r   r   s          r   :test_nonzero_returncode_empty_stderr_returns_error_messagezITestCallGemini.test_nonzero_returncode_empty_stderr_returns_error_message  s      hra8;X^A_` 	>()<==H	> 1<0H<<<<0H<<<0<<<<<<H<<<H<<<<<<< >	> 	>r   c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)rl   r   r   rm   r   r#   r%   rn   Nu8   ⚠️ Gemini에서 빈 응답이 반환되었습니다.r(   r*   r+   r,   r/   r0   r   r:   s          r   ro   z?TestCallGemini.test_empty_stdout_returns_empty_response_message  s      Xb);X^A_` 	A()?@@H	A VUxUUUUUxUUUUUUUxUUUxUUUUUUUUUUU A	A 	Arp   c                J  K   d}t        d|d      }t        dt        |            5  t        d       d	{   }d	d	d	       |v }|st	        j
                  d
|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            d	}y	7 # 1 sw Y   xY ww)uN   Gemini 에러는 stderr 필터링 없이 내용 전체를 반환해야 한다.z<warning: some warning
info: detail info
fatal error occurredr   rU   r   r   r#   r%   u   stderr 전체 포함 테스트NrX   r   r   r+   r   r   rz   )r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   )r;   r   r<   r+   r>   r   r   s          r   'test_error_includes_full_stderr_contentz6TestCallGemini.test_error_includes_full_stderr_content  s      Rhv!<;X^A_` 	K()IJJH	K !!!!v!!!!!!v!!!v!!!!!!!!!!!!!!!! K	K 	Ks,   (D#DDDCD#DD D#c                  K   t        dd      }t        |      }t        d|      5  t        dd	       d
{    d
d
d
       |j                  \  }}|j
                  }d} ||      }d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d
x}x}x}x}	}y
7 # 1 sw Y   xY ww)uG   timeout 파라미터가 CLIRunner.run_gemini에 전달되어야 한다.r   r   rm   r#   r   r%   r      rG   NrH   r(   r   rw   rx   r}   r~   )r   r   r   r   r   r   r2   r3   r4   r5   r6   r7   r8   r9   r   s               r   r   z2TestCallGemini.test_timeout_param_passed_to_runner  s     Xd+&1;J 	L=sKKK	L &&	6zz+)+z)$++$++++$++++++v+++v+++z+++)+++$+++++++++++ L	L 	Lr   c                   K   t        dd      }t        |      }d}t        d|      5  t        |       d{    ddd       |j                  \  }}|d	   }||k(  }|st        j                  d
|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}y7 # 1 sw Y   xY ww)u^   프롬프트 문자열이 CLIRunner.run_gemini에 첫 번째 인자로 전달되어야 한다.r   r   rm   r#   u#   Gemini에게 전달할 프롬프트r   r%   Nr   r(   r   r   r[   r/   r0   )r   r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r   s
             r   r   z+TestCallGemini.test_prompt_passed_to_runner  s      Xd+&16;J 	&f%%%	& $$aAw w&    w&   w      &   &        &	& 	&r   Nr   )r   r   r   r   r   r   r   rA   rK   rR   r   r   r   ro   r   r   r   r   r   r   r   r     s3   F [[? ? [[? ? [[> > [[	" 	" [[$ $ [[= = [[V V [[	" 	" [[	, 	, [[
! 
!r   r   c                  2   e Zd ZdZej
                  j                  dd       Zej
                  j                  dd       Zej
                  j                  dd       Z	ej
                  j                  dd       Z
ej
                  j                  dd       Zy)	TestSanitizeOutputIntegrationu/   bot_api.py에서 sanitize_output 적용 검증.c                D  K   d}t        d|      }t        dt        |            5  t        d       d{   }ddd       d	}|v}|st	        j
                  d
|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}y7 X# 1 sw Y   XxY ww)ub   call_claude()가 sanitize_output을 적용하여 시스템 프롬프트를 필터링해야 한다.u7   ## 역할
백엔드 개발자.

실제 답변입니다.r!   rm   r"   r#   r%   	   테스트Nu	   ## 역할r]   r_   r+   r[   r/   r0      실제 답변입니다rX   rZ   )r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r;   
raw_outputr<   r+   rb   r=   r?   r@   s           r   (test_call_claude_applies_sanitize_outputzFTestSanitizeOutputIntegration.test_call_claude_applies_sanitize_output  s     R
Xj1;X^A_` 	6(55H	6*{(****{(***{******(***(*******'3'83333'8333'333333833383333333 6	6 	6,   'F FFFEF FFF c                D  K   d}t        d|      }t        dt        |            5  t        d       d{   }ddd       d	}|v}|st	        j
                  d
|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}y7 X# 1 sw Y   XxY ww)u7   call_codex()가 sanitize_output을 적용해야 한다.u1   # 오딘 - 개발2팀장

실제 답변입니다.r   rm   r   r#   r%   r   Nu   오딘 - 개발2팀장r]   r_   r+   r[   r/   r0   r   rX   rZ   )r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r   s           r   'test_call_codex_applies_sanitize_outputzETestSanitizeOutputIntegration.test_call_codex_applies_sanitize_output  s
     K
WZ0:	W]@^_ 	5'44H	5'7'x7777'x777'777777x777x7777777'3'83333'8333'333333833383333333 5	5 	5r  c                D  K   d}t        d|      }t        dt        |            5  t        d       d{   }ddd       d	}|v}|st	        j
                  d
|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}y7 X# 1 sw Y   XxY ww)u8   call_gemini()가 sanitize_output을 적용해야 한다.u;   ## 작업 규칙
코드를 작성.

실제 답변입니다.r   rm   r   r#   r%   r   Nu   ## 작업 규칙r]   r_   r+   r[   r/   r0   r   rX   rZ   )r   r   r   r   r2   r3   r7   r4   r5   r6   r8   r9   r   s           r   (test_call_gemini_applies_sanitize_outputzFTestSanitizeOutputIntegration.test_call_gemini_applies_sanitize_output$  s
     V
Xj1;X^A_` 	6(55H	6!1!1111!111!1111111111111111'3'83333'8333'333333833383333333 6	6 	6r  c                  K   t        dd      }t        dt        |            5  t        dd       d	{   }d	d	d	       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	7 # 1 sw Y   xY ww)uH   에러 메시지에는 sanitize_output이 적용되지 않아야 한다.r!   rD   rE   r"   r#   r%   r   rG   NrI   r(   r*   r+   r,   r/   r0   rJ   r:   s          r   ,test_sanitize_does_not_affect_error_messageszJTestSanitizeOutputIntegration.test_sanitize_does_not_affect_error_messages.  s      (4;X^A_` 	C(cBBH	C>>x>>>>>x>>>>>>>x>>>x>>>>>>>>>>> C	C 	CrM   c                  K   t        dd      }t        dt        |            5  t        d       d{   }ddd       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7 # 1 sw Y   xY ww)u>   일반 텍스트는 sanitize 후에도 보존되어야 한다.r!   u;   안녕하세요, 도움이 필요하시면 말씀하세요.rm   r"   r#   r%   r   Nr(   r*   r+   r,   r/   r0   r1   r:   s          r   #test_sanitize_preserves_normal_textzATestSanitizeOutputIntegration.test_sanitize_preserves_normal_text6  s      X&cd;X^A_` 	6(55H	6XXxXXXXXxXXXXXXXxXXXxXXXXXXXXXXX 6	6 	6rp   Nr   )r   r   r   r   r   r   r   r   r  r  r  r	  r   r   r   r   r     s    9[[4 4 [[4 4 [[4 4 [[? ? [[Y Yr   r   )r   )r   strr   r
  r   r
  r   r
   )r   r   rU   )
r   r
  r   r
  r   r
  r   intr   r
   )rD   )r   r
  r   r  r   r
   )!r   
__future__r   builtinsr4   _pytest.assertion.rewrite	assertionrewriter2   ossyspathinsertjoindirname__file__unittest.mockr   r   r   engine_v2.bot_apir   r   r   engine_v2.cli_runnerr
   r   r   r   r   r   r   r   r   r   r   <module>r     s    #   	 
 277<< 94@ A *  B B * H! H!`w! w!~x! x!v/Y /Yr   