
    Li>                       d Z ddlmZ ddl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mZ ddlmZmZmZ ddlZddlmZ 	 	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 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   tests/test_engine_v2_phase5.py — Phase 5 TDD 테스트 스위트.

G13+G15: ChapterRunner, QCHook 검증.
작성 순서: 테스트 먼저(RED), 구현 후 GREEN 확인.
    )annotationsNz..)datetimetimezone)Path)	AsyncMock	MagicMockpatch)EngineResultc                f    t        || | |||||t        ddddddt        j                        	      S )u!   테스트용 EngineResult 생성.i           r   )tzinfo)	enginecontentcleantask_idsteperrorflagged_count	token_est	timestamp)r
   r   r   utc)r   r   r   r   r   r   r   s          J/home/jay/workspace/services/multimodel-bot/tests/test_engine_v2_phase5.py_make_resultr      sA     #4BAqF
 
    c                  J    e Zd ZdZej
                  j                  dd       Zy)TestChapterRunnerOutputDiru"   출력 디렉토리 생성 확인.c                  	
K   ddl m} t        d      t        dd      t        dd      t        d	d
      	t        dd      
dd	
fd} |dddd      }t        d|      5  t        j                  |j
                  d|      5  |j                          d{    ddd       ddd       |dz  }|j                         sJ |j                         sJ y7 ># 1 sw Y   =xY w# 1 sw Y   AxY ww)uB   run() 호출 시 chapter-N 디렉토리가 생성되어야 한다.r   ChapterRunneru   초안 내용)r      Gemini 초안geminir   r   u   집대성 결과u"   수정 불필요. 우수합니다.codexu   최종 결과claudec                T   K   |dk(  rgS |dk(  rgS |dk(  rgS |dk(  r	gS gS w)N      r       )
promptsenginesr   r   timeoutmock_result_geminimock_result_ok
mock_step2
mock_step3
mock_step5s
        r   fake_run_stepzXTestChapterRunnerOutputDir.test_chapter_runner_creates_output_dir.<locals>.fake_run_stepE   sP     qy&(:;;"|#"|#"|#"##s   %(c   z	task-testu	   가이드u   챕터 정보chapterr   guidechapter_info%publishing.chapter_runner._OUTPUT_DIRrun_stepside_effectNz
chapter-99i  returnlist[EngineResult])	publishing.chapter_runnerr!   r   r	   object_adapterrunexistsis_dir)selftmp_pathr!   r4   runnerchapter_dirr/   r0   r1   r2   r3   s         @@@@@r   &test_chapter_runner_creates_output_dirzATestChapterRunnerOutputDir.test_chapter_runner_creates_output_dir:   s      	<%o>)/(S!*<XN
!*NW^_
!/(K
		$ 		$ r;k`op :HE 	#fooz}U #jjl""#	# -!!###!!###	 ## #	# 	#sH   A*D1#C5C)(C')C)-C552D'C))C2	.C55C>:DNrI   r   r@   None)__name__
__module____qualname____doc__pytestmarkasynciorL   r+   r   r   r   r   7   s!    ,[[$ $r   r   c                  J    e Zd ZdZej
                  j                  dd       Zy)TestChapterRunnerStep1Parallelu-   Step 1이 claude+gemini 병렬 호출 확인.c                  K   ddl m} g ddfd} |dddd      }t        d|      5  t        j                  |j                  d	|
      5  |j                          d{    ddd       ddd       D cg c]  }|d   dk(  s| }}t        |      dk(  sJ d|d   d   v sJ d|d   d   v sJ y7 U# 1 sw Y   TxY w# 1 sw Y   XxY wc c}w w)u<   Step 1은 claude와 gemini를 동시에 호출해야 한다.r   r    c                  K   j                  |t        |      d       |dk(  rt        dd      t        dd      gS |dk(  rt        d	d      gS |d
k(  rt        dd      gS |dk(  rt        dd      gS t               gS w)Nr   r-   r(   u   Claude 초안r&   r$   r"   r#   r)   	   집대성r   "   수정 불필요. 충분합니다.r%   r*      최종appendlistr   r,   r-   r   r   r.   captured_callss        r   r4   zXTestChapterRunnerStep1Parallel.test_chapter_runner_step1_parallel.<locals>.fake_run_stepf   s     !!4DM"JKqy J J  $[JKK$-QZabcc$XhGHH N##   BBr(   z
task-step1 r6   r:   r;   r<   Nr   r&   r-   r#   r>   r?   rB   r!   r	   rC   rD   rE   len)rH   rI   r!   r4   rJ   cstep1_callsrb   s          @r   "test_chapter_runner_step1_parallelzATestChapterRunnerStep1Parallel.test_chapter_runner_step1_parallel_   s      	<%'	$ q,bWYZ:HE 	#fooz}U #jjl""#	#
 #1CQAfINqCC;1$$$;q>)4444;q>)4444 ## #	# 	#
 DsX   (C#CB:"B8#B:'C/C;C	C+C8B::C	?CCCNrM   )rO   rP   rQ   rR   rS   rT   rU   ri   r+   r   r   rW   rW   \   s!    7[[5 5r   rW   c                  J    e Zd ZdZej
                  j                  dd       Zy)!TestChapterRunnerStep5ClaudeFinalu,   Step 5가 claude 엔진으로 호출 확인.c                  K   ddl m} g ddfd} |dddd      }t        d|      5  t        j                  |j                  d	|
      5  |j                          d{    ddd       ddd       D cg c]  }|d   dk(  s| }}t        |      dk(  sJ |d   d   dgk(  sJ y7 K# 1 sw Y   JxY w# 1 sw Y   NxY wc c}w w)u9   Step 5는 반드시 claude 엔진을 사용해야 한다.r   r    c                  K   j                  |t        |      d       |dk(  rt        dd      t        dd      gS |dk(  rt        d	d      gS |d
k(  rt        dd      gS |dk(  rt        dd      gS t               gS w)NrZ   r(      초안1r&   r$      초안2r#   r)   r[   r   r\   r%   r*   u   최종 통합r^   ra   s        r   r4   z_TestChapterRunnerStep5ClaudeFinal.test_chapter_runner_step5_claude_final.<locals>.fake_run_step   s     !!4DM"JKqy 8D 8D  $[JKK$-QZabcc$_XNOO N##rc   r)   z
task-step5rd   r6   r:   r;   r<   Nr   r*   r(   r-   r&   r>   r?   re   )rH   rI   r!   r4   rJ   rg   step5_callsrb   s          @r   &test_chapter_runner_step5_claude_finalzHTestChapterRunnerStep5ClaudeFinal.test_chapter_runner_step5_claude_final   s      	<%'	$ q,bWYZ:HE 	#fooz}U #jjl""#	# #1CQAfINqCC;1$$$1~i(XJ666	 ## #	# 	# DsX   (C#B<B0"B.#B0'B</C;C	C!C.B00B9	5B<<CCNrM   )rO   rP   rQ   rR   rS   rT   rU   rq   r+   r   r   rk   rk      s!    6[[7 7r   rk   c                  J    e Zd ZdZej
                  j                  dd       Zy)$TestChapterRunnerConsensusStopsAtMaxu$   최대 3라운드 후 종료 확인.c                P  K   ddl m} ddlm} dddfd} |dddd      }t	        d	|      5  t	        j
                  |j                  d
|      5  |j                          d{    ddd       ddd       |k  sJ dk\  sJ y7 ## 1 sw Y   "xY w# 1 sw Y   &xY ww)uL   Step 3-4 반복이 MAX_CONSENSUS_ROUNDS(3) 이하로 수행되어야 한다.r   r    )MAX_CONSENSUS_ROUNDSc                  K   |dk(  rt        dd      t        dd      gS |dk(  rt        dd      gS |d	k(  rdz  t        d
d      gS |dk(  rt        dd      gS |dk(  rt        dd      gS t               gS w)Nr(   rn   r&   r$   ro   r#   r)   r[   r   u&   수정 필요. 오류가 있습니다.r%      u   반영된 내용r*   r]   )r   )r,   r-   r   r   r.   step3_call_counts        r   r4   zfTestChapterRunnerConsensusStopsAtMax.test_chapter_runner_consensus_stops_at_max.<locals>.fake_run_step   s     qy 8D 8D  $[JKK A% $-U^efgg$-?QRR$XhGHH N##s   A<A?r   ztask-consensusrd   r6   r:   r;   r<   Nr(   r>   r?   )rB   r!   publishing.consensus_pipelineru   r	   rC   rD   rE   )rH   rI   r!   ru   r4   rJ   rx   s         @r   *test_chapter_runner_consensus_stops_at_maxzOTestChapterRunnerConsensusStopsAtMax.test_chapter_runner_consensus_stops_at_max   s      	<F	$& q2B"[]^:HE 	#fooz}U #jjl""#	#
  #77771$$$	 ## #	# 	#sF   .B&#BB(B)B-B5B&BB	BB#B&NrM   )rO   rP   rQ   rR   rS   rT   rU   rz   r+   r   r   rs   rs      s!    .[["% "%r   rs   c                  0    e Zd ZdZddZddZddZddZy)TestParseArgsu   CLI 인수 파싱 확인.c                    ddl m}  |g d      }|j                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ |j
                  dk(  sJ y)uB   --chapter와 --task-id가 필수 인수로 파싱되어야 한다.r   
parse_args)	--chapter5	--task-idtask-007r*   r   rd   NrB   r   r7   r   r8   r9   rH   r   argss      r   test_parse_args_requiredz&TestParseArgs.test_parse_args_required   sZ    8EF||q   ||z)))zzR  B&&&r   c                    ddl m}  |g d      }|j                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ |j
                  dk(  sJ y)	uB   --guide와 --chapter-info 선택 인수가 파싱되어야 한다.r   r~   )r   3r   task-003z--guide   집필가이드 내용z--chapter-info   챕터 3 정보r   r   r   r   Nr   r   s      r   test_parse_args_optionalz&TestParseArgs.test_parse_args_optional   sb    8	
 ||q   ||z)))zz5555  $5555r   c                    ddl m} t        j                  t              5   |ddg       ddd       y# 1 sw Y   yxY w)u9   --chapter가 없으면 SystemExit이 발생해야 한다.r   r~   r   task-001NrB   r   rS   raises
SystemExitrH   r   s     r   &test_parse_args_missing_chapter_raisesz4TestParseArgs.test_parse_args_missing_chapter_raises   s3    8]]:& 	2Z01	2 	2 	2   4=c                    ddl m} t        j                  t              5   |ddg       ddd       y# 1 sw Y   yxY w)u9   --task-id가 없으면 SystemExit이 발생해야 한다.r   r~   r   1Nr   r   s     r   &test_parse_args_missing_task_id_raisesz4TestParseArgs.test_parse_args_missing_task_id_raises   s3    8]]:& 	+S)*	+ 	+ 	+r   Nr@   rN   )rO   rP   rQ   rR   r   r   r   r   r+   r   r   r|   r|      s    #'6*2+r   r|   c                       e Zd ZdZddZddZy)TestQCHookOnEngineCompleteu*   on_engine_complete() 파일 생성 확인.c                `   ddl m}  ||      }t        dddddd	      }|j                  |       |d
z  }|j	                         sJ t        j                  |j                  d            }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   du sJ |d   dk(  sJ |d   dk(  sJ d|v sJ y)uF   on_engine_complete() 호출 시 JSON 파일이 생성되어야 한다.r   
FileQCHook
output_diru   엔진 응답r&   ztask-qc-001r(   *   )r   r   r   r   r   r   ztask-qc-001_step1_claude.jsonutf-8encodingr   r   r   r   Fr   r   r   Nengine_v2.qc_hookr   r   on_engine_completerF   jsonloads	read_textrH   rI   r   hookresultexpected_filedatas          r   test_qc_hook_on_engine_completez:TestQCHookOnEngineComplete.test_qc_hook_on_engine_complete  s    0X.#!
 	' #BB##%%%zz-1171CDH~)))I-///F|q   G}%%%O$)))K B&&&d"""r   c                   ddl m}  ||      }t        ddddd	      }|j                  |       |d
z  }|j	                         sJ t        j                  |j                  d            }|d   du sJ |d   dk(  sJ y)u7   에러 결과도 JSON 파일로 기록되어야 한다.r   r   r   rd   r#   Tztask-qc-errr   )r   r   r   r   r   ztask-qc-err_step3_gemini.jsonr   r   r   r   Nr   r   s          r   ,test_qc_hook_on_engine_complete_error_resultzGTestQCHookOnEngineComplete.test_qc_hook_on_engine_complete_error_result$  s    0X.!
 	' #BB##%%%zz-1171CDG}$$$H~)))r   NrM   )rO   rP   rQ   rR   r   r   r+   r   r   r   r     s    4#8*r   r   c                  (    e Zd ZdZddZddZddZy)TestQCHookOnPipelineCompleteu2   on_pipeline_complete() summary.json 생성 확인.c           	        ddl m}  ||      }t        dddd      t        d	d
dd      t        ddddd      g}|j                  |       |dz  }|j	                         sJ t        j                  |j                  d            }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ d|d   v sJ d
|d   v sJ d|d   v sJ y)uJ   on_pipeline_complete() 호출 시 _summary.json이 생성되어야 한다.r   r   r      결과1r&   ztask-pipe-001r(   )r   r   r   r      결과2r#   r)   rd   r%   r   T)r   r   r   r   r   ztask-pipe-001_summary.jsonr   r   r   total_stepserrorsengines_usedN)r   r   r   on_pipeline_completerF   r   r   r   )rH   rI   r   r   resultsr   r   s          r   !test_qc_hook_on_pipeline_completez>TestQCHookOnPipelineComplete.test_qc_hook_on_pipeline_complete>  s    0X.8_[\]8_[\]G_ST\`a
 	!!'* #??##%%%zz-1171CDI/111M"a'''H~"""4////4////$~....r   c                    ddl m}  ||      }|j                  g        t        |j	                               g k(  sJ y)uZ   빈 결과 목록에서 on_pipeline_complete()가 파일을 생성하지 않아야 한다.r   r   r   N)r   r   r   r`   iterdir)rH   rI   r   r   s       r   'test_qc_hook_on_pipeline_complete_emptyzDTestQCHookOnPipelineComplete.test_qc_hook_on_pipeline_complete_emptyV  s;    0X.!!"% H$$&'2---r   c                    ddl m}  ||      }t        ddddd	      t        d
dddd	      g}|j                  |       t	        j
                  |dz  j                  d            }|d   dk(  sJ y)u@   total_flagged가 flagged_count 합계로 기록되어야 한다.r   r   r   r   r&   z	task-flagr(   r)   )r   r   r   r   r   r   r#   r   ztask-flag_summary.jsonr   r   total_flaggedr*   N)r   r   r   r   r   r   r   )rH   rI   r   r   r   r   s         r   /test_qc_hook_on_pipeline_complete_flagged_totalzLTestQCHookOnPipelineComplete.test_qc_hook_on_pipeline_complete_flagged_total`  s    0X.8[WXhij8[WXhij

 	!!'*zz8&>>IISZI[\O$)))r   NrM   )rO   rP   rQ   rR   r   r   r   r+   r   r   r   r   ;  s    </0.*r   r   c                  (    e Zd ZdZddZddZddZy)TestQCHandlerProtocolu4   QCHandler Protocol 인터페이스 적합성 확인.c                    ddl m}m}  ||      }t        |d      sJ t        |d      sJ t	        |j
                        sJ t	        |j                        sJ y)u8   FileQCHook이 QCHandler Protocol을 충족해야 한다.r   )r   	QCHandlerr   r   r   N)r   r   r   hasattrcallabler   r   )rH   rI   r   r   r   s        r   "test_qc_handler_protocol_file_hookz8TestQCHandlerProtocol.test_qc_handler_protocol_file_hooks  s[    ;X. t1222t3444//00011222r   c                    ddl m}  G d d      } |       }t               }|j                  |       |j	                  |g       t        |j                        dk(  sJ |j                  du sJ y)u>   커스텀 QCHandler 구현이 Protocol을 충족해야 한다.r   )r   c                  (    e Zd ZdZddZddZddZy)	]TestQCHandlerProtocol.test_qc_handler_protocol_custom_implementation.<locals>.CustomQCHandleru   커스텀 QC 핸들러.c                     g | _         d| _        y )NF)	completedpipeline_done)rH   s    r   __init__zfTestQCHandlerProtocol.test_qc_handler_protocol_custom_implementation.<locals>.CustomQCHandler.__init__  s    57+0"r   c                :    | j                   j                  |       y )N)r   r_   )rH   r   s     r   r   zpTestQCHandlerProtocol.test_qc_handler_protocol_custom_implementation.<locals>.CustomQCHandler.on_engine_complete  s    %%f-r   c                    d| _         y )NT)r   )rH   r   s     r   r   zrTestQCHandlerProtocol.test_qc_handler_protocol_custom_implementation.<locals>.CustomQCHandler.on_pipeline_complete  s
    %)"r   Nr   )r   r
   r@   rN   )r   rA   r@   rN   )rO   rP   rQ   rR   r   r   r   r+   r   r   CustomQCHandlerr     s    )1.*r   r   r(   TN)r   r   r   r   r   rf   r   r   )rH   rI   r   r   customr   s         r   .test_qc_handler_protocol_custom_implementationzDTestQCHandlerProtocol.test_qc_handler_protocol_custom_implementation  sk    /	* 	* !"!!&)##VH-6##$)))##t+++r   c                z    ddl m} |dz  dz  }|j                         rJ  ||      }|j                         sJ y)u=   FileQCHook이 output_dir을 자동으로 생성해야 한다.r   r   nestedqcr   N)r   r   rF   )rH   rI   r   new_dirr   s        r   -test_qc_hook_creates_output_dir_automaticallyzCTestQCHandlerProtocol.test_qc_hook_creates_output_dir_automatically  s>    0X%,>>###W-~~r   NrM   )rO   rP   rQ   rR   r   r   r   r+   r   r   r   r   p  s    >
3,2 r   r   )u   정상 응답r&   Fr   r(   r   
   )r   strr   r   r   boolr   r   r   intr   r   r   r   r@   r
   )rR   
__future__r   r   ossyspathinsertjoindirname__file__r   r   pathlibr   unittest.mockr   r   r	   rS   engine_v2.engine_resultr
   r   r   rW   rk   rs   r|   r   r   r   r+   r   r   <module>r      s   #  	 
 277<< 94@ A '  5 5  0 #  	
    8"$ "$J#5 #5L!7 !7H&% &%R.+ .+l3* 3*l2* 2*j0  0 r   