
    (<i                       d Z ddlZddlZddlZddlZddlZddlmZmZ ddlm	Z	 ddl
mZmZ ddlZde	dej                  fdZ ej                          d	        Z ej                          d
        Z	 	 	 	 	 	 d)dededz  dededededz  defdZde	de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 G d# d$      Z  G d% d&      Z! G d' d(      Z"y)*u*  
test_chain_manager.py

chain_manager.py 순차 작업 체이닝 관리 유틸리티 단위 테스트

테스트 항목:
- TestCreate: 정상 생성, max_tasks 초과 거부, 중복 chain_id 거부
- TestNext: 다음 pending 반환, QC FAIL 시 stalled, chain_complete 반환, no_chain 반환, 동일 task_file 중복 차단
- TestUpdate: running/done/failed/stalled 상태 변경
- TestCheckStalled: 정체 작업 검출, 정체 없음
- TestList: 체인 목록 출력
- TestLock: 동시 접근 시 순차 처리
- TestBackup: .bak 파일 생성 확인
    N)datetime	timedelta)Path)	MagicMockpatchtmp_pathreturnc                    t        t        j                  j                  dd            }t	        |      t
        j                  vr)t
        j                  j                  dt	        |             t        t
        j                  j                               D ]  }|dk(  s	t
        j                  |=  t        j                  j                  dd       ddl}| |_        | dz  d	z  |_        |j                  j                  d
d
       |S )uK   chain_manager 모듈을 tmp_path를 WORKSPACE로 설정하여 로드한다.WORKSPACE_ROOTz/home/jay/workspacer   chain_managerCOKACDIR_KEY_ANUztest-dummy-keyNmemorychainsTparentsexist_ok)r   osenvirongetstrsyspathinsertlistmoduleskeys
setdefaultr   	WORKSPACE
CHAINS_DIRmkdir)r   	workspacemod_namecms       I/home/jay/workspace/.worktrees/task-2057-dev2/tests/test_chain_manager.py_load_chain_managerr%       s    RZZ^^$46KLMI
9~SXX%3y>* ))+, &&H%& JJ,.>?BLx'(2BMMMt4I    c                     t        |       S )uI   격리된 WORKSPACE를 사용하는 chain_manager 모듈을 반환한다.)r%   )r   s    r$   r#   r#   <   s     x((r&   c                 <    | dz  dz  }|j                  dd       |S )u"   체인 파일 저장 디렉토리.r   r   Tr   )r    )r   ds     r$   
chains_dirr*   B   s(     	8h&AGGD4G(Hr&   chain_idtasksstatus	max_tasksscopewatchdog_cron_idc           	          |dddddddddg}| dt        j                         j                         ||||d	}|||d
<   |S )u.   테스트용 체인 데이터를 생성한다.N   zmemory/tasks/dispatch-001.md	dev1-teampendingautoorder	task_fileteamr-   task_idgate
started_atcompleted_atanur+   
created_by
created_atr-   r/   r.   r,   r0   )r   now	isoformat)r+   r,   r-   r.   r/   r0   datas          r$   _make_chain_datarE   O   st     } ;##" $	
 lln..0D ##3 Kr&   r*   rD   c                     |d   }|j                  d      r| d}nd| d}| |z  }|j                  t        j                  |dd      d       |S )	uB   체인 데이터를 파일로 저장하고 경로를 반환한다.r+   chain-.jsonF   ensure_asciiindentutf-8encoding)
startswith
write_textjsondumps)r*   rD   r+   filenamer   s        r$   _write_chainrU   s   s`    JH8$Zu%H:U+ DOODJJt%BWOUKr&   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)
TestCreateu&   cmd_create() 서브커맨드 테스트c                    ddddddddd	dg}t        j                  d
t        j                  |      ddd      }t	        j
                  |d      5 }t               }d|_        d|_        ||j                  _
        |j                  |       ddd       |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k(  sJ |	d   dk(  sJ t        |	d         dk(  sJ |	d   d   }
|
d   dk(  sJ |
d   J |
d   J |
d    J y# 1 sw Y   xY w)!uD   정상 생성: 체인 파일이 올바른 스키마로 생성된다.r2   memory/tasks/t1.mdr3   r5   r7   r8   r9   r;   rI   zmemory/tasks/t2.md	dev2-teamnonezchain-20260307-001   테스트 범위r>   
   r+   r,   r/   r@   r.   
subprocessr   z{"cron_id": "cron-abc123"}Nzchain-chain-20260307-001.jsonrM   rN   r+   r-   activer/   r@   r.   r,   r4   r:   r<   r=   )argparse	NamespacerR   rS   r   objectr   
returncodestdoutrunreturn_value
cmd_createexistsloads	read_textlen)selfr#   r   r*   r,   argsmock_submock_result
chain_filerD   ts              r$   test_create_normalzTestCreate.test_create_normal   s    &:KY_`&:KY_`
 !!)**U#$
 \\"l+ 	 x#+K%&K"!=K(3HLL%MM$	   "AA
  """zz*...@AJ#7777H~)))G} 2222L!U***K B&&&4=!Q&&&M!{i'''|###&&& (((+	  	 s   ;D>>Ec                    dddddg}t        j                  dt        j                  |      ddd	
      }t	        j
                  |d      5 }t               }d|_        t        j                  ddi      |_        ||j                  _
        |j                  |       |j                  j                  sJ 	 ddd       |dz  }t        j                  |j                  d            }	d|	v sJ y# 1 sw Y   :xY w)ub   create 성공 시 cokacdir watchdog cron이 등록되고 cron_id가 체인 파일에 저장된다.r2   rY   r3   r5   rZ   zchain-cron-testu   cron 테스트r>   r^   r_   r`   r   cron_idzcron-watchdog-001Nzchain-chain-cron-test.jsonrM   rN   r0   )rb   rc   rR   rS   r   rd   r   re   rf   rg   rh   ri   calledrk   rl   
rn   r#   r   r*   r,   ro   rp   rq   rr   rD   s
             r$   #test_create_registers_watchdog_cronz.TestCreate.test_create_registers_watchdog_cron   s    +?^def!!&**U#"
 \\"l+ 	'x#+K%&K"!%Y8K,L!MK(3HLL%MM$ <<&&&&	'  ">>
zz*...@A!T)))	' 	's   A(C..C7c                 d   t        d      D cg c]  }|dz   d| dddd }}t        j                  dt        j                  |      d	d
d      }t        j                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ yc c}w # 1 sw Y   *xY w)u,   max_tasks 초과 시 exit 1이 발생한다.   r2   memory/tasks/t.mdr3   r5   rZ   zchain-overflowu   초과 테스트r>      r_   N)rangerb   rc   rR   rS   pytestraises
SystemExitri   valuecode)rn   r#   r   ir,   ro   exc_infos          r$   test_create_max_tasks_exceededz)TestCreate.test_create_max_tasks_exceeded   s     1X
 !eN1#S*A;`fg
 
 !!%**U#$
 ]]:& 	 (MM$	 ~~""a'''
	  	 s   B!+B&&B/c                    dddddg}t        j                  dt        j                  |      ddd	
      }t	        j
                  |d      5 }t               }d|_        d|_        ||j                  _
        |j                  |       ddd       t        j                  t              5 }|j                  |       ddd       j                  j                   dk(  sJ y# 1 sw Y   XxY w# 1 sw Y   1xY w)u9   동일 chain_id 중복 생성 시 exit 1이 발생한다.r2   rY   r3   r5   rZ   z	chain-dupu   중복 테스트r>   r^   r_   r`   r   z{"cron_id": "cron-001"}N)rb   rc   rR   rS   r   rd   r   re   rf   rg   rh   ri   r   r   r   r   r   )	rn   r#   r   r*   r,   ro   rp   rq   r   s	            r$   test_create_duplicate_chain_idz)TestCreate.test_create_duplicate_chain_id   s    +?^def!! **U#$
 \\"l+ 	 x#+K%&K"!:K(3HLL%MM$	  ]]:& 	 (MM$	 ~~""a'''	  	 	  	 s   ;C(C*C'*C3c                     t        j                  ddddd      }t        j                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)	u0   잘못된 tasks JSON 시 exit 1이 발생한다.zchain-bad-jsonz{not valid json}u   JSON 오류 테스트r>   r^   r_   Nr2   )rb   rc   r   r   r   ri   r   r   )rn   r#   r   ro   r   s        r$   test_create_invalid_tasks_jsonz)TestCreate.test_create_invalid_tasks_json   sj    !!%$)
 ]]:& 	 (MM$	 ~~""a'''	  	 s   A**A3N)	__name__
__module____qualname____doc__rt   ry   r   r   r    r&   r$   rW   rW      s    0#)J*2($(.(r&   rW   c                       e Zd ZdZ	 	 	 ddedededz  dedef
dZd	 Zd
 Z	d Z
d Zd Zd Zd Zd Zd Zd Zd Zd Zy)TestNextu$   cmd_next() 서브커맨드 테스트Nr*   r+   r,   chain_statusr	   c           
      .   |6ddddddt        j                         j                         ddd	d
dddddddg}|dt        j                         j                         |ddd|d}|| dz  }|j                  t	        j
                  |dd	      d       |S )u2   테스트용 체인 파일을 생성하는 헬퍼.Nr2   memory/tasks/task-001.mdr3   running	task-10.1r5   r6   rI   memory/tasks/task-002.mdr[   r4   r>   u	   테스트r^   zcron-watch-001r+   r@   rA   r-   r/   r.   r0   r,   rH   FrJ   rM   rN   r   rB   rC   rQ   rR   rS   )rn   r*   r+   r,   r   rD   r   s          r$   _setup_chainzTestNext._setup_chain  s     = !;''*""*,,.":":"<$(	 !;''#""&$(	E. !",,.224"  0	
 xj..

4eAFQXYr&   c                 |   | j                  |       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd	
       |j                  |       |j                         }t        j                  |j                        }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ d|v sJ y)uA   다음 pending task가 있으면 action=dispatch를 반환한다.r   r:   r   reportsTr   task-10.1.mdu   작업 완료. 성공.rM   rN   actiondispatchr8   r   r9   r[   r+   N
r   rb   rc   r    rQ   cmd_next
readouterrrR   rk   out	rn   r#   r   r*   capsysro   reports_dircapturedoutputs	            r$   test_next_returns_dispatchz#TestNext.test_next_returns_dispatch2  s    *%!!+6 )I5$6	~	%112JU\1]
D$$&HLL)h:---k"&@@@@f~,,,V###r&   c                 *   | j                  |       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd	
       |j                  |       |j                         }t        j                  |j                        }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |dz  }	t        j                  |	j                  d	
            }
|
d   dk(  sJ t        d |
d   D              }|d   dk(  sJ |d   dk(  sJ y)u]   gate=auto 조건에서 보고서에 FAIL 키워드가 있으면 재위임(retry)한다 (F12).r   r   r   r   Tr   r   u&   작업 완료. QC FAIL: 오류 발견.rM   rN   r   r   retry_attemptr2   r:   chain-test.jsonr-   ra   c              3   J   K   | ]  }|j                  d       dk(  s|  ywr:   r   Nr   .0rs   s     r$   	<genexpr>z<TestNext.test_next_qc_fail_triggers_retry.<locals>.<genexpr>]  s      WquuY7G;7V1W   ##r,   retry_countr   N)r   rb   rc   r    rQ   r   r   rR   rk   r   rl   next)rn   r#   r   r*   r   ro   r   r   r   rr   rD   target_tasks               r$    test_next_qc_fail_triggers_retryz)TestNext.test_next_qc_fail_triggers_retryE  s:   *%!!+6 )I5$6	~	%112Zel1m
D$$&HLL)h:---o&!+++i K///  "33
zz*...@AH~))) Wd7mWW=)Q...8$	111r&   c                 H   | j                  |       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd	
       |j                  |       |j                         }t        j                  |j                        }|d   dk(  sJ y)uQ   gate=auto 조건에서 'QC FAIL (범위 외)'는 체인을 stall하지 않는다.r   r   r   r   Tr   r   uQ   작업 완료. QC FAIL (범위 외 기존 테스트 1건 — 에스컬레이션).rM   rN   r   r   Nr   r   s	            r$   -test_next_qc_fail_out_of_scope_does_not_stallz6TestNext.test_next_qc_fail_out_of_scope_does_not_stallb  s    *%!!+6 )I5$6	~	%11_jq 	2 	
 	D$$&HLL)h:---r&   c           
         ddddddt        j                         j                         ddd	d
dddddddg}| j                  ||       t	        j
                  d      }|dz  dz  }|j                  dd       |dz  j                  dd       |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ y)uU   gate=none이면 보고서 내용에 관계없이 다음 pending task를 반환한다.r2   r   r3   r   	task-20.1r\   Nr6   rI   r   r[   r4   r,   r   r   r   Tr   task-20.1.mdu&   QC FAIL 가 있어도 무시해야 함rM   rN   r   r   r   rB   rC   r   rb   rc   r    rQ   r   r   rR   rk   r   
rn   r#   r   r*   r   r,   ro   r   r   r   s
             r$   test_next_gate_none_skips_qcz%TestNext.test_next_gate_none_skips_qct  s     7##&&lln668 $	 7##" $	
, 	*E2!!+6 )I5$6	~	%112Zel1m
D$$&HLL)h:---r&   c           	         ddddddt        j                         j                         ddg}| j                  ||	       t	        j
                  d
      }|dz  dz  }|j                  dd       |dz  j                  dd       t        j                  |d      5 }t               }	d|	_        d|	_        |	|j                  _        |j                  |       ddd       |j!                         }
t#        j$                  |
j&                        }|d   dk(  sJ d|v sJ |dz  }t#        j$                  |j)                  d            }|d   dk(  sJ y# 1 sw Y   }xY w)ua   다음 pending task가 없으면 action=chain_complete를 반환하고 체인이 completed된다.r2   r   r3   r   z	task-30.1r5   Nr6   r   r   r   r   Tr   ztask-30.1.md   작업 완료.rM   rN   r`   r    r   chain_completer+   r   r-   	completed)r   rB   rC   r   rb   rc   r    rQ   r   rd   r   re   rf   rg   rh   r   r   rR   rk   r   rl   )rn   r#   r   r*   r   r,   ro   r   rp   rq   r   r   rr   rD   s                 r$   test_next_chain_completez!TestNext.test_next_chain_complete  sj    7##&&lln668 $	
 	*E2!!+6)I5$6	~	%112BW1U\\"l+ 	x#+K%&K"!#K(3HLL%KK	 $$&HLL)h#3333V###  "33
zz*...@AH~,,,	 	s   ;EEc           	      f   ddddddt        j                         j                         ddg}| j                  ||	       t	        j
                  d
      }|dz  dz  }|j                  dd       |dz  j                  dd       t        j                  |d      5 }t               }d|_        d|_        ||j                  _        |j                  |       |j                  j                   D 	cg c]  }	t#        |	       }
}	t%        d |
D              sJ 	 ddd       yc c}	w # 1 sw Y   yxY w)uQ   chain_complete 시 watchdog cron이 제거된다 (cokacdir --cron-remove 호출).r2   r   r3   r   z	task-31.1r5   Nr6   r   r   r   r   Tr   ztask-31.1.md   완료.rM   rN   r`   r   r   c              3   $   K   | ]  }d |v  
 yw)zcron-removeNr   )r   cmds     r$   r   zGTestNext.test_next_removes_watchdog_cron_on_complete.<locals>.<genexpr>  s     C}+Cs   )r   rB   rC   r   rb   rc   r    rQ   r   rd   r   re   rf   rg   rh   r   call_args_listr   any)rn   r#   r   r*   r,   ro   r   rp   rq   callcalled_cmdss              r$   +test_next_removes_watchdog_cron_on_completez4TestNext.test_next_removes_watchdog_cron_on_complete  s1    7##&&lln668 $	
 	*E2!!+6)I5$6	~	%11)g1N\\"l+ 		Dx#+K%&K"!#K(3HLL%KK 2:1L1LM3t9MKMC{CCCC		D 		D N		D 		Ds   AD'0D"D'"D''D0c                     t        j                  d      }|j                  |       |j                         }t	        j
                  |j                        }|d   dk(  sJ |d   dk(  sJ y)uR   어떤 체인에도 없는 task_id이면 action=no_chain을 반환한다 (exit 0).ztask-unknown-999r   r   no_chainr:   N)rb   rc   r   r   rR   rk   r   rn   r#   r   r*   r   ro   r   r   s           r$   test_next_no_chainzTestNext.test_next_no_chain  sf    !!*<=
D$$&HLL)h:---i $6666r&   c           
         ddddddt        j                         j                         ddd	dd
ddddddg}| j                  ||       t	        j
                  d      }|dz  dz  }|j                  dd       |dz  j                  dd       |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ d|	d   v sJ y)ui   이미 running/done 상태인 task의 task_file과 동일한 task_file을 가진 pending이 차단된다.r2   zmemory/tasks/same-file.mdr3   r   z	task-40.1r5   Nr6   rI   r[   r4   r   r   r   r   Tr   ztask-40.1.mdr   rM   rN   r   stalledzduplicate task_filereasonr   r   s
             r$   %test_next_duplicate_task_file_blockedz.TestNext.test_next_duplicate_task_file_blocked  s    8##&&lln668 $	 8##" $	
, 	*E2!!+6)I5$6	~	%11)g1N
D$$&HLL)h9,,,$x(8888r&   c                 v   | j                  |       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd	
       |j                  |       |dz  }t        j                  |j                  d	
            }t        d |d   D              }|d   dk(  sJ |d   J y)uL   next 호출 시 완료된 task의 status=done, completed_at이 설정된다.r   r   r   r   Tr   r   r   rM   rN   r   c              3   J   K   | ]  }|j                  d       dk(  s|  ywr   r   r   s     r$   r   z8TestNext.test_next_marks_completed_at.<locals>.<genexpr>  s      UqQUU95E5TUr   r,   r-   doner=   N)
r   rb   rc   r    rQ   r   rR   rk   rl   r   )	rn   r#   r   r*   ro   r   rr   rD   	done_tasks	            r$   test_next_marks_completed_atz%TestNext.test_next_marks_completed_at  s    *%!!+6)I5$6	~	%11)g1N
D"33
zz*...@AUDMUU	"f,,,(444r&   c                    | j                  |       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd	
       |j                  |       |j                         }t        j                  |j                        }|d   dk(  sJ |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ y)uW   이미 done인 task에 대해 next를 다시 호출하면 already_done을 반환한다.r   r   r   r   Tr   r   r   rM   rN   r   r   already_doneNr   )
rn   r#   r   r*   r   ro   r   r   output1output2s
             r$   "test_next_idempotency_already_donez+TestNext.test_next_idempotency_already_done#  s    *%!!+6)I5$6	~	%11)g1N 	D$$&**X\\*x J... 	D$$&**X\\*x N222r&   c                 p   ddddddddd	ddd
ddg}| j                  ||       t        j                  d      }|dz  dz  }|j                  dd       |dz  j	                  dd       |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ y)u=   다음 task가 이미 running이면 dispatch하지 않는다.r2   zmemory/tasks/task-10.1.mdr3   r   r   r5   r7   r8   r9   r-   r:   r;   rI   zmemory/tasks/task-10.2.mdz	task-10.2r   r   r   r   Tr   r   r   rM   rN   r   already_runningNr   r   s
             r$   *test_next_idempotency_next_already_runningz3TestNext.test_next_idempotency_next_already_running8  s    
 8##& 8##&
$ 	*E2!!+6)I5$6	~	%11)g1N
D$$&HLL)h#4444r&   c           	      Z   |dz  dz  dz  }|j                   j                  dd       |j                  dd       d	d
ddddt        j                         j                         ddddddddg}|dz  }ddt        j                         j                         dddd|d}|j                  t        j                  |dd      d       t        j                  d      }	|dz  d z  }
|
j                  dd       |
d!z  j                  d"d       |j                  |	       |dz  dz  d#z  }|j                         sJ d$       |j                  d      |j                  d      k(  sJ |j                         }t        j                  |j                        }|d%   d&k(  sJ |d'   dk(  sJ y())u>   task_file이 없을 때 original_task_file에서 복사한다.r   r,   z
task-20.mdTr   u-   # 원본 지시서

Phase 1: ...
Phase 2: ...rM   rN   r2   zmemory/tasks/task-20.1.mdr3   r   r   r5   )r7   r8   r9   r-   r:   r;   r<   rI   zmemory/tasks/task-20.2.mdr4   z	task-20.2r   r   
chain-testr>   ra   r   r^   zmemory/tasks/task-20.md)r+   r@   rA   r-   r/   r.   original_task_filer,   FrJ   r   r   r   r   ztask-20.2.mdu6   Phase 2 지시서 파일이 자동 생성되어야 함r   r   r8   N)parentr    rQ   r   rB   rC   rR   rS   rb   rc   r   rj   rl   r   rk   r   )rn   r#   r   r*   r   r   r,   rr   rD   ro   r   phase2_task_filer   r   s                 r$   1test_next_creates_missing_task_file_from_originalz:TestNext.test_next_creates_missing_task_file_from_originalZ  s    &07:\I!!''t'D%%&Xcj%k
 8##&&lln668 8##&
&  "33
$",,.224";	
 	djjE!LW^_ !!+6)I5$6	~	%11)g1N
D $h.8>I&&(b*bb(  ))7);?Q?[?[el?[?mmmm $$&HLL)h:---k"&AAAAr&   )r   Nra   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r&   r$   r   r     s    .
 %!$++ + d{	+
 + 
+Z$&2:.$$.L%-ND@	7$9L5"3* 5D<Br&   r   c                   D    e Zd ZdZddededefdZd Zd Zd Z	d	 Z
d
 Zy)
TestUpdateu&   cmd_update() 서브커맨드 테스트r*   r:   r	   c           	          dddd|ddddg}dd	t        j                         j                         d
ddd|d}|dz  }|j                  t	        j
                  |dd      d       |S )u2   update 테스트용 체인 파일을 생성한다.r2   zmemory/tasks/task-x.mdr3   r4   r5   Nr6   zchain-update-testr>   ra   u   업데이트 테스트r^   cron-001r   chain-update-test.jsonFrI   rJ   rM   rN   r   )rn   r*   r:   r,   rD   r   s         r$   _setup_chain_with_taskz!TestUpdate._setup_chain_with_task  s     5##"" $	
 ,",,.224- *	
 44

4eAFQXYr&   c                     | j                  |       t        j                  dd      }|j                  |       |dz  }t	        j
                  |j                  d            }|d   d   }|d	   dk(  sJ |d
   J y)u;   running으로 상태 변경 시 started_at이 설정된다.	task-50.1r   r:   r-   r   rM   rN   r,   r   r-   r<   Nr   rb   rc   
cmd_updaterR   rk   rl   rn   r#   r   r*   ro   rr   rD   tasks           r$   test_update_to_runningz!TestUpdate.test_update_to_running  s    ##J/!!+iH
d"::
zz*...@AG}QH~***L!---r&   c                     | j                  |       t        j                  dd      }|j                  |       |dz  }t	        j
                  |j                  d            }|d   d   }|d	   dk(  sJ |d
   J y)u:   done으로 상태 변경 시 completed_at이 설정된다.r   r   r   r   rM   rN   r,   r   r-   r=   Nr   r   s           r$   test_update_to_donezTestUpdate.test_update_to_done  s    ##J/!!+fE
d"::
zz*...@AG}QH~'''N#///r&   c                     | j                  |       t        j                  dd      }|j                  |       |dz  }t	        j
                  |j                  d            }|d   d   }|d	   dk(  sJ y
)u@   failed로 상태 변경 시 status=failed로 업데이트된다.r   failedr   r   rM   rN   r,   r   r-   Nr   r   s           r$   test_update_to_failedz TestUpdate.test_update_to_failed  su    ##J/!!+hG
d"::
zz*...@AG}QH~)))r&   c                     | j                  |       t        j                  dd      }|j                  |       |dz  }t	        j
                  |j                  d            }|d   d   }|d	   dk(  sJ y
)uB   stalled로 상태 변경 시 status=stalled로 업데이트된다.r   r   r   r   rM   rN   r,   r   r-   Nr   r   s           r$   test_update_to_stalledz!TestUpdate.test_update_to_stalled  su    ##J/!!+iH
d"::
zz*...@AG}QH~***r&   c                    | j                  |       t        j                  dd      }t        j                  t
              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uD   존재하지 않는 task_id 업데이트 시 exit 1이 발생한다.ztask-nonexistentr   r   Nr2   )	r   rb   rc   r   r   r   r   r   r   )rn   r#   r   r*   ro   r   s         r$    test_update_task_not_found_exitsz+TestUpdate.test_update_task_not_found_exits  sj    ##J/!!*<VL]]:& 	 (MM$	 ~~""a'''	  	 s   A88BN)r   )r   r   r   r   r   r   r   r   r   r   r  r  r   r&   r$   r   r     s9    0  VZ 8
.
0	*	+(r&   r   c            	       F    e Zd ZdZ	 	 ddedededefdZd Zd Z	d	 Z
d
 Zy)TestCheckStalledu-   cmd_check_stalled() 서브커맨드 테스트r*   r+   started_hours_agor	   c           	      4   t        j                         t        |      z
  j                         }dddddd|dd	g}|d
t        j                         j                         dddd|d}|| dz  }|j	                  t        j                  |dd      d       |S )u<   running 상태 task가 있는 체인 파일을 생성한다.hoursr2   zmemory/tasks/running-task.mdr[   r   	task-60.1r5   Nr6   r>   ra   u   정체 테스트r^   r   r   rH   FrI   rJ   rM   rN   )r   rB   r   rC   rQ   rR   rS   )rn   r*   r+   r  r<   r,   rD   r   s           r$   _setup_chain_with_running_taskz/TestCheckStalled._setup_chain_with_running_task  s     llny7H'IITTV
 ;##&( $	
 !",,.224' *	
 xj..

4eAFQXYr&   c                    | j                  |d       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }t        |t              sJ t        |      dk(  sJ |d   d   dk(  sJ |d   d	   d
k(  sJ |d   d   dk(  sJ |d   d   dk\  sJ y)u3   max_hours 초과한 running 작업이 검출된다.      @r  rI   	max_hoursr2   r   r+   chain-stalled-testr:   r  r9   r[   hours_elapsedNr  rb   rc   cmd_check_stalledr   rR   rk   r   
isinstancer   rm   r   s           r$   "test_check_stalled_detects_stalledz3TestCheckStalled.test_check_stalled_detects_stalled  s    ++J#+N!!A.
T"$$&HLL)&$'''6{aay$(<<<<ay#{222ay K///ay)Q...r&   c                    | j                  |d       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }t        |t              sJ t        |      dk(  sJ y)u=   max_hours 미만인 running 작업은 검출되지 않는다.g      ?r  rI   r  r   Nr  r   s           r$   test_check_stalled_no_stalledz.TestCheckStalled.test_check_stalled_no_stalled-  su    ++J#+N!!A.
T"$$&HLL)&$'''6{ar&   c                     t        j                  d      }|j                  |       |j                         }t	        j
                  |j                        }|g k(  sJ y)u6   활성 체인이 없으면 빈 배열을 출력한다.rI   r  N)rb   rc   r  r   rR   rk   r   r   s           r$   test_check_stalled_empty_resultz0TestCheckStalled.test_check_stalled_empty_result8  sL    !!A.
T"$$&HLL)||r&   c           	         t        j                         t        d      z
  j                         }dddddd|d	d
g}ddt        j                         j                         ddd|d}|dz  }|j	                  t        j                  |dd      d       t        j                  d      }	|j                  |	       |j                         }
t        j                  |
j                        }|g k(  sJ y	)uE   completed/stalled 체인의 task는 정체 검사에서 제외된다.r{   r	  r2   rY   r3   r   z	task-70.1r5   Nr6   zchain-completedr>   r   u   완료 체인r^   r?   zchain-completed.jsonFrI   rJ   rM   rN   r  )r   rB   r   rC   rQ   rR   rS   rb   rc   r  r   rk   r   )rn   r#   r   r*   r   r<   r,   rD   r   ro   r   r   s               r$   )test_check_stalled_skips_completed_chainsz:TestCheckStalled.test_check_stalled_skips_completed_chainsA  s    llnyq'99DDF
 1##&( $	
 *",,.224!$
 22

4eAFQXY!!A.
T"$$&HLL)||r&   N)r  r  )r   r   r   r   r   r   floatr  r  r  r  r  r   r&   r$   r  r    sM    7
 -#&	     !	 
 
 D/	  r&   r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestListu$   cmd_list() 서브커맨드 테스트c                     t        j                         }|j                  |       |j                         }t	        j
                  |j                        }|g k(  sJ y)u0   체인이 없을 때 빈 배열을 출력한다.N)rb   rc   cmd_listr   rR   rk   r   r   s           r$   test_list_emptyzTestList.test_list_emptyl  sH    !!#
D$$&HLL)||r&   c                 ~   t        d      D ]"  }t        d|dd|       }t        ||       $ t        j                         }|j                  |       |j                         }t        j                  |j                        }	t        |	      dk(  sJ |	D 
cg c]  }
|
d   	 }}
d|v sJ d|v sJ d	|v sJ y
c c}
w )u:   여러 체인이 있을 때 모두 목록에 포함된다.r~   rG   03du   범위 )r+   r/   r+   z	chain-000z	chain-001z	chain-002N)r   rE   rU   rb   rc   r"  r   rR   rk   r   rm   )rn   r#   r   r*   r   r   rD   ro   r   r   item	chain_idss               r$   test_list_multiple_chainsz"TestList.test_list_multiple_chainsu  s    q 	+A#vaW-=wqc]SDT*	+ !!#
D$$&HLL)6{a289$T*%9	9i'''i'''i''' :s   B:c                 6   t        d      }t        ||       t        j                         }|j	                  |       |j                         }t        j                  |j                        }t        |      dk(  sJ |d   }	d|	v sJ d|	v sJ d|	v sJ d|	v sJ y	)
uL   목록의 각 항목에 chain_id, status, scope, tasks 수 필드가 있다.zchain-fields)r+   r2   r   r+   r-   r/   
task_countN)
rE   rU   rb   rc   r"  r   rR   rk   r   rm   )
rn   r#   r   r*   r   rD   ro   r   r   r&  s
             r$   test_list_shows_required_fieldsz(TestList.test_list_shows_required_fields  s    8Z&!!#
D$$&HLL)6{aayT!!!4$t###r&   c                 R   t        d      D cg c]  }|dz   d| dddddddd	 }}t        d
|      }t        ||       t        j                         }|j                  |       |j                         }	t        j                  |	j                        }
|
d   d   dk(  sJ yc c}w )u<   task_count가 실제 tasks 배열의 길이와 일치한다.   r2   r|   r}   r3   r4   Nr5   r6   zchain-count)r+   r,   r   r*  )
r   rE   rU   rb   rc   r"  r   rR   rk   r   )rn   r#   r   r*   r   r   r,   rD   ro   r   r   s              r$   test_list_task_count_correctz%TestList.test_list_task_count_correct  s     1X
  Q-aS4##" $	
 
  eDZ&!!#
D$$&HLL)ay&!++++
s   B$N)r   r   r   r   r#  r(  r+  r.  r   r&   r$   r   r   i  s    .("$",r&   r   c                       e Zd ZdZd Zd Zy)TestLocku@   lock 파일을 이용한 원자적 상태 업데이트 테스트c           	      L   dddddddddg}d	d
t        j                         j                         dddd|d}|dz  }|j                  t	        j
                  |dd      d       t        j                  dd      }|j                  |       |dz  }|j                         rJ y)uL   상태 업데이트 시 lock 파일이 생성되고 완료 후 삭제된다.r2   zmemory/tasks/lock-test.mdr3   r4   z	task-80.1r5   Nr6   zchain-lock-testr>   ra   u   락 테스트r^   r   r   zchain-lock-test.jsonFrI   rJ   rM   rN   r   r   zchain-lock-test.lock
r   rB   rC   rQ   rR   rS   rb   rc   r   rj   )	rn   r#   r   r*   r,   rD   rr   ro   	lock_files	            r$   "test_lock_file_created_and_removedz+TestLock.test_lock_file_created_and_removed  s     8##&" $	
 *",,.224$ *	
  "88
djjE!LW^_!!+iH
d !77	##%%%%r&   c                    t        d      D cg c]  }|dz   d| ddddd|z    d	d
ddd }}ddt        j                         j                         dddd|d}|dz  }|j	                  t        j                  |dd      d       t        d      D ]1  }t        j                  dd|z    d	d      }|j                  |       3 t        j                  |j                  d            }	|	d   D ]  }
|
d   dk(  rJ  yc c}w )u6   순차 업데이트 시 최종 상태가 정확하다.r~   r2   r|   r}   r3   r4   ztask-Z   z.1r5   Nr6   zchain-seq-testr>   ra   u   순차 테스트r^   r   r   zchain-seq-test.jsonFrI   rJ   rM   rN   r   r   r,   r-   )r   r   rB   rC   rQ   rR   rS   rb   rc   r   rk   rl   )rn   r#   r   r*   r   r,   rD   rr   ro   
final_datar   s              r$   #test_sequential_updates_consistencyz,TestLock.test_sequential_updates_consistency  sC    1X
  Q-aS4##"26("-" $	
 
 )",,.224' *	
  "77
djjE!LW^_ q 	 A%%b1fXR.@PDMM$	  ZZ
 4 4g 4 FG
w' 	,D>V+++	,A
s    DN)r   r   r   r   r4  r8  r   r&   r$   r0  r0    s    J &D#,r&   r0  c                   "    e Zd ZdZd Zd Zd Zy)
TestBackupu*   체인 파일 .bak 백업 기능 테스트c           	      L   dddddddddg}d	d
t        j                         j                         dddd|d}|dz  }|j                  t	        j
                  |dd      d       t        j                  dd      }|j                  |       |dz  }|j                         sJ y)u-   .bak 파일이 update 직전에 생성된다.r2   zmemory/tasks/bak-test.mdr3   r4   
task-100.1r5   Nr6   zchain-bak-testr>   ra   u   백업 테스트r^   r   r   zchain-bak-test.jsonFrI   rJ   rM   rN   r   r   zchain-bak-test.json.bakr2  )	rn   r#   r   r*   r,   rD   rr   ro   bak_files	            r$   test_backup_created_on_updatez(TestBackup.test_backup_created_on_update	  s     7##'" $	
 )",,.224' *	
  "77
djjE!LW^_!!,yI
d 99   r&   c           	         dddddddddg}d	d
t        j                         j                         dddd|d}|dz  }t        j                  |dd      }|j                  |d       t        j                  dd      }|j                  |       |dz  }	t        j                  |	j                  d            }
|
d   d   d   dk(  sJ y)uA   .bak 파일에 업데이트 전 원본 데이터가 저장된다.r2   z memory/tasks/bak-content-test.mdr3   r4   z
task-101.1r5   Nr6   zchain-bak-contentr>   ra   u   백업 내용 테스트r^   r   r   zchain-bak-content.jsonFrI   rJ   rM   rN   r   r   zchain-bak-content.json.bakr,   r   r-   )r   rB   rC   rR   rS   rQ   rb   rc   r   rk   rl   )rn   r#   r   r*   r,   rD   rr   original_textro   r=  bak_datas              r$   "test_backup_contains_original_dataz-TestBackup.test_backup_contains_original_data*  s     ?##'" $	
 ,",,.224. *	
  "::


4eAFmg>!!,yI
d <<::h00'0BC #H-:::r&   c           	         ddddddt        j                         j                         ddg}d	d
t        j                         j                         dddd|d}|dz  }|j                  t	        j
                  |dd      d       |dz  dz  }|j                  dd       |dz  j                  dd       t        j                  |d      5 }t               }	d|	_
        d|	_        |	|j                  _        t        j                  d      }
|j!                  |
       ddd       |d z  }|j#                         sJ y# 1 sw Y   !xY w)!u6   cmd_next 호출 시에도 .bak 파일이 생성된다.r2   zmemory/tasks/bak-next-test.mdr3   r   z
task-102.1r5   Nr6   zchain-bak-nextr>   ra   u   next 백업 테스트r^   r   r   zchain-bak-next.jsonFrI   rJ   rM   rN   r   r   Tr   ztask-102.1.mdr   r`   r   r   r   zchain-bak-next.json.bak)r   rB   rC   rQ   rR   rS   r    r   rd   r   re   rf   rg   rh   rb   rc   r   rj   )rn   r#   r   r*   r,   rD   rr   r   rp   rq   ro   r=  s               r$   test_backup_created_on_nextz&TestBackup.test_backup_created_on_nextN  sP    <##'&lln668 $	
 )",,.224, *	
  "77
djjE!LW^_)I5$6		&229w2O\\"l+ 	x#+K%&K"!#K(3HLL%%%l;DKK	  99   	 	s   AD>>EN)r   r   r   r   r>  rB  rD  r   r&   r$   r:  r:    s    4!B";H(!r&   r:  c                   ^    e Zd ZdZdedededefdZd Zd Z	d	 Z
d
 Zd Zd Zd Zd Zd Zy)	TestChecku5   cmd_check() 서브커맨드 테스트 (읽기 전용)r*   r+   r,   r	   c           	          |dt        j                         j                         dddd|d}|d| dz  }|j                  t	        j
                  |d	d
      d       |S )u+   테스트용 체인 파일을 생성한다.r>   ra   u   check 테스트r^   r   r   rG   rH   FrI   rJ   rM   rN   r   )rn   r*   r+   r,   rD   r   s         r$   r   zTestCheck._setup_chain  sj     !",,.224& *	
 fXJe44

4eAFQXYr&   c           
         ddddddt        j                         j                         ddd	d
dddddddg}| j                  |d|       t	        j
                  d      }|j                  |       |j                         }t        j                  |j                        }|d   du sJ |d   du sJ |d   dk(  sJ |d   dk(  sJ y)ub   체인 소속이고 마지막이 아닌 경우: in_chain=true, is_last=false, next_task_id 반환.r2   memory/tasks/dispatch-436.2.mdr3   r   
task-436.2r5   Nr6   rI   zmemory/tasks/dispatch-436.3.mdr[   r4   z
task-436.3zremotion-migrationr   in_chainTis_lastFr+   next_task_idr   rB   rC   r   rb   rc   	cmd_checkr   rR   rk   r   	rn   r#   r   r*   r   r,   ro   r   r   s	            r$   test_check_in_chain_not_lastz&TestCheck.test_check_in_chain_not_last  s     =##'&lln668 $	 =##'" $	
, 	*&:EB!!,7
T$$&HLL)j!T)))i E)))j!%9999n%555r&   c           
         ddddddt        j                         j                         t        j                         j                         ddd	d
dddt        j                         j                         ddg}| j                  |d|       t	        j
                  d      }|j                  |       |j                         }t        j                  |j                        }|d   du sJ |d   du sJ |d   dk(  sJ |d   J y)uX   체인 소속이고 마지막인 경우: in_chain=true, is_last=true, next_task_id=null.r2   zmemory/tasks/dispatch-436.1.mdr3   r   z
task-436.1r5   r6   rI   rI  r[   r   rJ  Nzremotion-migration-lastr   rK  TrL  r+   rM  rN  rP  s	            r$   test_check_in_chain_is_lastz%TestCheck.test_check_in_chain_is_last  s    =# '&lln668 ( 8 8 :	 =##'&lln668 $	
, 	*&?G!!,7
T$$&HLL)j!T)))i D(((j!%>>>>n%---r&   c                     t        j                  d      }|j                  |       |j                         }t	        j
                  |j                        }|d   du sJ |d   du sJ |d   J |d   J y)	uU   어떤 체인에도 없는 task_id: in_chain=false, chain_id=null, next_task_id=null.ztask-nonexistent-9999r   rK  FrL  r+   NrM  )rb   rc   rO  r   rR   rk   r   r   s           r$   test_check_not_in_chainz!TestCheck.test_check_not_in_chain  s    !!*AB
T$$&HLL)j!U***i E)))j!)))n%---r&   c           
         ddddddt        j                         j                         ddd	d
dddddddg}| j                  |d|       t	        j
                  d      }|j                  |       |j                         }t        j                  |j                        }|d   du sJ |d   du sJ |d   dk(  sJ |d   J y)uN   다음 task가 pending이고 task_id=None인 경우: next_task_id=null 반환.r2   zmemory/tasks/dispatch-500.1.mdr3   r   z
task-500.1r5   Nr6   rI   zmemory/tasks/dispatch-500.2.mdr[   r4   zpending-no-id-chainr   rK  TrL  Fr+   rM  rN  rP  s	            r$   )test_check_next_task_id_none_when_pendingz3TestCheck.test_check_next_task_id_none_when_pending  s     =##'&lln668 $	 =##" $	
, 	*&;UC!!,7
T$$&HLL)j!T)))i E)))j!%::::n%---r&   c                    ddddddddd	dg}t        j                  d
t        j                  |      ddd      }t	        j
                  |d      5 }t               }d|_        d|_        ||j                  _
        |j                  |       ddd       |dz  }t        j                  |j                  d            }	|	d   d   d   dk(  sJ |	d   d   d   dk(  sJ y# 1 sw Y   TxY w)uB   create 시 tasks JSON의 task_id가 체인 파일에 보존된다.r<  r2   zmemory/tasks/test.mdr3   r:   r7   r8   r9   z
task-100.2rI   zmemory/tasks/test2.mdr[   zpreserve-id-testu   task_id 보존 테스트r>   r^   r_   r`   r   z{"cron_id": "cron-preserve"}Nzchain-preserve-id-test.jsonrM   rN   r,   r:   )rb   rc   rR   rS   r   rd   r   re   rf   rg   rh   ri   rk   rl   rx   s
             r$   test_create_preserves_task_idz'TestCheck.test_create_preserves_task_id  s    %q?U_jk$q?V`kl
 !!'**U#,
 \\"l+ 	 x#+K%&K"!?K(3HLL%MM$	   "??
zz*...@AG}Q	*l:::G}Q	*l:::	  	 s   ;C  C)c                 \   ddddddddd	dg}t        j                  d
t        j                  |      ddd      }t	        j
                  |d      5 }t               }d|_        d|_        ||j                  _
        |j                  |       ddd       |j                          t        j                  d      }	|j                  |	       |j                         }
t        j                  |
j                        }|d   du sJ |d   du sJ |d   d
k(  sJ |d   dk(  sJ y# 1 sw Y   xY w)uI   create로 생성한 체인에서 task_id로 check 매칭이 성공한다.z
task-200.1r2   zmemory/tasks/t200-1.mdr3   rY  z
task-200.2rI   zmemory/tasks/t200-2.mdr[   zcheck-find-testu   check 매칭 테스트r>   r^   r_   r`   r   z{"cron_id": "cron-find"}Nr   rK  TrL  Fr+   rM  )rb   rc   rR   rS   r   rd   r   re   rf   rg   rh   ri   r   rO  rk   r   )rn   r#   r   r*   r   r,   args_createrp   rq   
args_checkr   r   s               r$   !test_check_finds_chain_by_task_idz+TestCheck.test_check_finds_chain_by_task_id   s>    %q?Walm$q?Walm
 ((&**U#*
 \\"l+ 	'x#+K%&K"!;K(3HLL%MM+&	' 	''=

Z $$&HLL)j!T)))i E)))j!%6666n%555#	' 	's   ;D""D+c           
         ddddddt        j                         j                         ddd	d
dddddddg}| j                  |d|       t	        j
                  d      }|dz  dz  }|j                  dd       |dz  j                  dd       |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ d|	v sJ d       |	d   dk(  sJ y)uE   cmd_next의 dispatch action 출력에 task_id 필드가 포함된다.r2   zmemory/tasks/task-566.1.mdr3   r   z
task-566.1r5   Nr6   rI   zmemory/tasks/task-566.2.mdr4   z
task-566.2zchain-566-testr   r   r   Tr   ztask-566.1.mdr   rM   rN   r   r   r:   u2   dispatch action 출력에 task_id 필드가 없음r   r   s
             r$   $test_next_includes_task_id_in_outputz.TestCheck.test_next_includes_task_id_in_outputA  s    9##'&lln668 $	 9##'" $	
, 	*&6>!!,7)I5$6		&223Cg2V
D$$&HLL)h:---F"X$XX"i L000r&   c           
         ddddddt        j                         j                         ddd	d
dddddddg}| j                  |d|       t	        j
                  d      }|dz  dz  }|j                  dd       |dz  j                  dd       |j                  |       |j                         }t        j                  |j                        }	|	d   dk(  sJ d|	v sJ |	d   J y)uW   다음 pending task의 task_id가 None이면 출력의 task_id도 None이어야 한다.r2   zmemory/tasks/task-567.1.mdr3   r   z
task-567.1r5   Nr6   rI   zmemory/tasks/task-567.2.mdr4   zchain-567-testr   r   r   Tr   ztask-567.1.mdr   rM   rN   r   r   r:   r   r   s
             r$   -test_next_task_id_none_when_pending_has_no_idz7TestCheck.test_next_task_id_none_when_pending_has_no_idi  s    9##'&lln668 $	 9##" $	
, 	*&6>!!,7)I5$6		&223Cg2V
D$$&HLL)h:---F"""i (((r&   c                    ddddddddd	dg}t        j                  d
t        j                  |      ddd      }t	        j
                  |d      5 }t               }d|_        d|_        ||j                  _
        |j                  |       ddd       t        j                  dd      }	|j                  |	       |j                          |dz  dz  }
|
j                  dd       |
dz  j                  dd       t        j                  d      }|j!                  |       |j                         }t        j"                  |j$                        }|d   dk(  sJ |d    dk(  sJ |d!   d	k(  sJ |d"z  }t        j"                  |j'                  d            }t)        d# |d$   D              }|d%   d&k(  sJ |d'   J y# 1 sw Y   8xY w)(uH   create로 생성한 체인에서 task_id로 next 진행이 성공한다.
task-300.1r2   zmemory/tasks/t300-1.mdr3   rY  z
task-300.2rI   zmemory/tasks/t300-2.mdr[   znext-advance-testu   next 진행 테스트r>   r^   r_   r`   r   z{"cron_id": "cron-advance"}Nr   r   r   r   Tr   ztask-300.1.mdr   rM   rN   r   r   r   r8   r9   zchain-next-advance-test.jsonc              3   J   K   | ]  }|j                  d       dk(  s|  yw)r:   rd  Nr   r   s     r$   r   z:TestCheck.test_next_advances_by_task_id.<locals>.<genexpr>  s      VqQUU95E5UVr   r,   r-   r   r=   )rb   rc   rR   rS   r   rd   r   re   rf   rg   rh   ri   r   r   r    rQ   r   rk   r   rl   r   )rn   r#   r   r*   r   r,   r\  rp   rq   args_updater   	args_nextr   r   rr   rD   r   s                    r$   test_next_advances_by_task_idz'TestCheck.test_next_advances_by_task_id  s    %q?Walm$q?Walm
 (((**U#)
 \\"l+ 	'x#+K%&K"!>K(3HLL%MM+&	' ((iP
k" )I5$6		&223Cg2V &&|<	
I$$&HLL)h:---k"&>>>>f~,,,  "@@
zz*...@AVDMVV	"f,,,(444C	' 	's   ;GGN)r   r   r   r   r   r   r   r   rQ  rS  rU  rW  rZ  r^  r`  rb  rh  r   r&   r$   rF  rF  ~  sY    ?t s 4 D  !6F!.F
.!.F;46B&1P%)N/5r&   rF  c                   h    e Zd ZdZ	 	 	 ddedededededefdZd	 Zd
 Z	d Z
d Zd Zd Zd Zd Zy)TestF12RetryPhaseu-   F12: completion-promise retry phase 테스트r*   r   r:   r   report_contentr	   c           
         dddd|dt        j                         j                         d|d	dd	dd
dddddg}ddt        j                         j                         dddd|d}|dz  }|j                  t	        j
                  |dd      d       |dz  dz  }	|	j                  dd       |	| dz  j                  |d       |S )u;   F12 테스트용 체인 + 보고서를 생성하는 헬퍼.r2   zmemory/tasks/task-f12-1.mdr[   r   r5   N	r7   r8   r9   r-   r:   r;   r<   r=   r   rI   zmemory/tasks/task-f12-2.mdr4   
task-f12.2r6   chain-f12-testr>   ra   u   F12 retry 테스트r^   zcron-f12r   chain-f12-test.jsonFrJ   rM   rN   r   r   Tr   r}   )r   rB   rC   rQ   rR   rS   r    )
rn   r*   r   r:   r   rk  r,   rD   r   r   s
             r$   _setup_chain_with_retryz)TestF12RetryPhase._setup_chain_with_retry  s     9##"&lln668 $*
 9##'" $	
0 )",,.224* *	
 11

4eAFQXY)I5$6	'#	&22>G2Tr&   c                    | j                  ||d       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }|d   dk(  sJ |d   dk(  sJ t        j                  |d	z  j                  d
            }|d   dk(  sJ t        d |d   D              }	|	d   dk(  sJ |	d   dk(  sJ y)uK   retry_count=0인 상태에서 QC FAIL → action=dispatch, retry_attempt=1.r   r   
task-f12.1r   r   r   r   r2   rp  rM   rN   r-   ra   c              3   J   K   | ]  }|j                  d       dk(  s|  ywr:   rt  Nr   r   s     r$   r   zCTestF12RetryPhase.test_f12_first_qc_fail_retries.<locals>.<genexpr>        XquuY7G<7W1Xr   r,   r   r   N
rq  rb   rc   r   r   rR   rk   r   rl   r   
rn   r#   r   r*   r   ro   r   r   rD   r   s
             r$   test_f12_first_qc_fail_retriesz0TestF12RetryPhase.test_f12_first_qc_fail_retries  s    $$Zq$I!!,7
D$$&HLL)h:---o&!+++ zz:(==HHRYHZ[H~))) Xd7mXX8$	111=)Q...r&   c                    | j                  ||d       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }|d   dk(  sJ |d   dk(  sJ t        j                  |d	z  j                  d
            }|d   dk(  sJ t        d |d   D              }	|	d   dk(  sJ y)uK   retry_count=1인 상태에서 QC FAIL → action=dispatch, retry_attempt=2.r2   rs  rt  r   r   r   r   rI   rp  rM   rN   r-   ra   c              3   J   K   | ]  }|j                  d       dk(  s|  ywrv  r   r   s     r$   r   zDTestF12RetryPhase.test_f12_second_qc_fail_retries.<locals>.<genexpr>#  rw  r   r,   r   Nrx  ry  s
             r$   test_f12_second_qc_fail_retriesz1TestF12RetryPhase.test_f12_second_qc_fail_retries  s    $$Zq$I!!,7
D$$&HLL)h:---o&!+++ zz:(==HHRYHZ[H~))) Xd7mXX=)Q...r&   c                    | j                  ||d       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }|d   dk(  sJ d|d   v sJ d	|v sJ t        j                  |d
z  j                  d            }|d   dk(  sJ |dz  dz  dz  }	|	j                         s
J d|	        y)uM   retry_count=2(=MAX_RETRY)인 상태에서 QC FAIL → circuit breaker 발동.rI   rs  rt  r   r   r   circuit_breakerr   escalation_filerp  rM   rN   r-   r   escalationstask-f12.1_escalation.jsonu*   escalation 파일이 생성되어야 함: N)
rq  rb   rc   r   r   rR   rk   r   rl   rj   )
rn   r#   r   r*   r   ro   r   r   rD   r  s
             r$   %test_f12_circuit_breaker_on_max_retryz7TestF12RetryPhase.test_f12_circuit_breaker_on_max_retry&  s    $$Zq$I!!,7
D$$&HLL)h9,,, F8$4444 F*** zz:(==HHRYHZ[H~*** #X-=@\\%%'g+UVeUf)gg'r&   c                    | j                  ||d       t        j                  d      }|j                  |       |j	                          |dz  dz  dz  }|j                         sJ t        j                  |j                  d	            }d
|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ |d
   dk(  sJ |d   dk(  sJ |d   dk(  sJ y)u;   circuit breaker 발동 후 escalation 파일 내용 검증.rI   rs  rt  r   r   r  r  rM   rN   r:   r+   triggered_atr   	max_retrytotal_attemptsr   ro  N)	rq  rb   rc   r   r   rj   rR   rk   rl   )rn   r#   r   r*   r   ro   r  contents           r$    test_f12_escalation_file_contentz2TestF12RetryPhase.test_f12_escalation_file_content;  s   $$Zq$I!!,7
D"X-=@\\%%'''**_666HIG###W$$$(((7"""g%%%7***7"""y!\111z"&6666{#q(((r&   c                 &    |j                   dk(  sJ y)u   MAX_RETRY == 2 확인.rI   N)	MAX_RETRY)rn   r#   r   r*   s       r$    test_f12_max_retry_constant_is_2z2TestF12RetryPhase.test_f12_max_retry_constant_is_2T  s    ||q   r&   c                    | j                  ||dd       t        j                  d      }|j                  |       |j	                         }t        j                  |j                        }|d   dk(  sJ |d   d	k(  sJ d
|vsJ y)uR   retry_count=1인 상태에서 QC PASS → action=dispatch (다음 task로 진행).r2   u'   작업 완료. 모든 테스트 통과.)r   rk  rt  r   r   r   r:   rn  r   N)rq  rb   rc   r   r   rR   rk   r   r   s           r$    test_f12_qc_pass_no_retry_neededz2TestF12RetryPhase.test_f12_qc_pass_no_retry_neededX  s    $$D	 	% 	
 !!,7
D$$&HLL)h:---i L000f,,,r&   c                    | j                  ||d       t        j                  d      }t        j                         j                         }|j                  |       |j                          t        j                  |dz  j                  d            }t        d |d	   D              }|d
   dk(  sJ |d   J |d   J |d   |k\  sJ y)uU   retry 후 target_task의 status=running, completed_at=None, started_at 갱신 확인.r   rs  rt  r   rp  rM   rN   c              3   J   K   | ]  }|j                  d       dk(  s|  ywrv  r   r   s     r$   r   zFTestF12RetryPhase.test_f12_retry_resets_task_status.<locals>.<genexpr>u  rw  r   r,   r-   r   r=   Nr<   )rq  rb   rc   r   rB   rC   r   r   rR   rk   rl   r   )	rn   r#   r   r*   r   ro   beforerD   r   s	            r$   !test_f12_retry_resets_task_statusz3TestF12RetryPhase.test_f12_retry_resets_task_statusk  s    $$Zq$I!!,7))+
Dzz:(==HHRYHZ[Xd7mXX8$	111>*222<(444<(F222r&   c           
      f   ddddddt        j                         j                         ddd		d
ddddddddg}ddt        j                         j                         dddd|d}|dz  }|j                  t	        j
                  |dd
      d       |dz  dz  }|j                  dd       |dz  j                  d d       t        j                  d!      }	|j                  |	       |j                         }
t	        j                  |
j                        }|d"   d#k(  sJ |d$   dk(  sJ d%|vsJ y)&uI   gate=none인 task는 QC FAIL이어도 retry 없이 다음 task로 진행.r2   z"memory/tasks/task-f12-gate-none.mdr[   r   ztask-f12-gate.1r\   Nr   rm  rI   z$memory/tasks/task-f12-gate-none-2.mdr4   ztask-f12-gate.2r6   zchain-f12-gate-noner>   ra   u   gate=none 테스트r^   zcron-f12-gater   zchain-f12-gate-none.jsonFrJ   rM   rN   r   r   Tr   ztask-f12-gate.1.mdu   QC FAIL: 오류 발견.r   r   r   r:   r   )r   rB   rC   rQ   rR   rS   r    rb   rc   r   r   rk   r   )rn   r#   r   r*   r   r,   rD   rr   r   ro   r   r   s               r$   !test_f12_gate_none_bypasses_retryz3TestF12RetryPhase.test_f12_gate_none_bypasses_retry}  sh    A##,&lln668 $ 
 C##," $	
0 .",,.224* /	
  "<<
djjE!LW^_ )I5$6	+	+778Q\c7d!!*;<
D$$&HLL)h:---i $5555f,,,r&   N)rt  r   u   QC FAIL: 타입 에러 발견.)r   r   r   r   r   r   intrq  rz  r}  r  r  r  r  r  r  r   r&   r$   rj  rj    sy    7 $>11 1 	1
 1 1 
1f/*/(h*)2!-&3$3-r&   rj  c                   "    e Zd ZdZd Zd Zd Zy)TestCircuitBreakerIntegrationu6   circuit_breaker 모듈 통합 테스트 (task-1651.1).c                    t        |      }|dz  dz  }|j                  ddd       |dz  }|j                         sJ d       t        j                  |j                               }|d   dk(  sJ |d	   dk(  sJ |d
   dk(  sJ d|d   v sJ y)ug   _trigger_circuit_breaker가 circuit_breaker 모듈의 _write_escalation_file을 호출하는지 검증.r   r  ztask-test-1zchain-test-1r~   ztask-test-1_escalation.jsonu+   escalation 파일이 생성되어야 한다r:   r+   r   
escalationr  r   N)r%   _trigger_circuit_breakerrj   rR   rk   rl   )rn   r   r#   escalations_dirr  rD   s         r$   (test_trigger_uses_circuit_breaker_modulezFTestCircuitBreakerIntegration.test_trigger_uses_circuit_breaker_module  s     *"X-=
##M>1E),II%%'V)VV'zz/3356I-///J>111H~---d8n,,,r&   c                 j   t        |      }|dz  dz  }t        |dd      }t        |dd       	 |j                  ddd       t        |d|       |dz  }|j	                         sJ d	       t        j                  |j                               }|d
   dk(  sJ |d   dk(  sJ y# t        |d|       w xY w)u`   circuit_breaker 모듈 미사용 시 fallback으로 escalation 파일이 생성되는지 검증.r   r  _CB_AVAILABLEFztask-fallback-1z
chain-fb-1r~   ztask-fallback-1_escalation.jsonu:   fallback으로 escalation 파일이 생성되어야 한다r:   r+   N)r%   getattrsetattrr  rj   rR   rk   rl   )rn   r   r#   r  original_cbr  rD   s          r$   )test_trigger_fallback_when_cb_unavailablezGTestCircuitBreakerIntegration.test_trigger_fallback_when_cb_unavailable  s     *"X-= b/59OU+	6''(9<KB5),MM%%'e)ee'zz/3356I"3333J</// B5s   B# #B2c                    ddl }ddlm} |j                  |       |j                  }|dz  |_        	 |j                  ddddd	       ||_        t        |dz  j                  d
            }t        |      dk(  sJ t        j                  |d   j                               }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ y# ||_        w xY w)uN   _write_escalation_file이 **extra 필드를 payload에 포함하는지 검증.r   Nr  
test_extraztest reasonr~   zchain-extracustom_value)contextr   error_countr+   custom_fieldz*_escalation.jsonr2   r+   r  r  r  )	importlibutils.circuit_breakerr  reloadESCALATIONS_DIR_write_escalation_filer   globrm   rR   rk   rl   )rn   r   r  cb_modoriginal_dirfilesrD   s          r$   'test_write_escalation_file_extra_fieldszETestCircuitBreakerIntegration.test_write_escalation_file_extra_fields  s    .  --!)M!9		2))$$&+ *  &2F" h.445HIJ5zQzz%(,,./J=000N#~555I,...M"a''' &2F"s   C 	CN)r   r   r   r   r  r  r  r   r&   r$   r  r    s    @-0&(r&   r  )z
test-chainNra   r^   r]   r   )#r   rb   rR   r   r   typesr   r   pathlibr   unittest.mockr   r   r   
ModuleTyper%   fixturer#   r*   r   r   r  dictrE   rU   rW   r   r   r  r   r0  r:  rF  rj  r  r   r&   r$   <module>r     s{     	 
  (  * $ 5+;+; 8 ) )
   !##-!!$;! ! 	!
 ! Dj! 
!H
T 
 
$ 
$u( u(zTB TBxS( S(vh h`E, E,ZH, H,`p! p!pA5 A5R
i- i-b@( @(r&   