
    (<iC9                       d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlZ e ee      j                         j                  j                  j                  j                        Zeej"                  vrej"                  j%                  de       ddlmZ  G d dej*                        Z G d	 d
ej*                        Z G d dej*                        Z G d dej*                        Z G d dej*                        Z G d dej*                        Z G d dej*                        Zedk(  r ej<                          yy)u  test_capture.py - capture 모듈 TDD 테스트 (RED → GREEN)

capture_input() 함수:
- 스킬 실행 시 사용된 user_input을 skills/<skill_name>/evals/test-inputs.yaml 에 저장
- 중복 방지 (text 동일하면 스킵)
- FIFO 최대 20개 유지
- id는 "input-YYYYMMDD-HHMMSS" 형식
- evals 디렉토리 없으면 자동 생성
- 예외 발생 시 raise하지 않고 None 반환 (경고만 출력)
    )annotationsN)Path)patch)capture_inputc                  (    e Zd ZdZddZddZddZy)TestCaptureInputCreateuJ   테스트 1: 빈 상태에서 입력 캡처 → test-inputs.yaml 생성됨c                    t        j                         | _        d| _        t	        | j                        | j                  z  }|j                  d       y Nad-creativeTparentstempfilemkdtemptmpdir
skill_namer   mkdirself	skill_dirs     X/home/jay/workspace/.worktrees/task-2057-dev2/scripts/autoresearch/tests/test_capture.pysetUpzTestCaptureInputCreate.setUp"   s>    &&('%7	%    c                    t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }| j	                  |j                         d|        y)uR   빈 상태에서 capture_input 호출 시 test-inputs.yaml 파일이 생성된다.6   보험 FA 모집 광고, 타겟: 30대 보험설계사r   
user_input
skills_direvalstest-inputs.yamlu0   test-inputs.yaml 파일이 생성되어야 함: N)r   r   r   r   
assertTrueexists)r   	yaml_paths     r   test_creates_test_inputs_yamlz4TestCaptureInputCreate.test_creates_test_inputs_yaml)   s`    O{{	
 %7'ADVV		((*.^_h^i,jkr   c                   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  d|d       | j                  |d   t               | j                  t        |d         d	       |d   d
   }| j                  d|       | j                  d|       | j                  |d   d       y)uN   생성된 YAML이 올바른 구조를 가진다 (inputs 키 + id/text 필드).r   r   r   r    utf-8encodinginputsu/   YAML 최상위에 'inputs' 키가 있어야 함   r   idtextN)r   r   r   r   yaml	safe_load	read_textassertInassertIsInstancelistassertEquallen)r   r#   dataentrys       r   test_yaml_has_correct_structurez6TestCaptureInputCreate.test_yaml_has_correct_structure4   s    O{{	
 %7'ADVV	~~i1171CDh&WXd8nd3T(^,a0Xq!dE"fe$v(`ar   NreturnNone)__name__
__module____qualname____doc__r   r$   r7    r   r   r   r      s    T&	lbr   r   c                  (    e Zd ZdZddZddZddZy)TestCaptureInputAppenduC   테스트 2: 기존 입력에 추가 → 리스트 끝에 추가됨c                   t        j                         | _        d| _        t	        | j                        | j                  z  dz  }|j                  d       ddddgi}|d	z  }|j                  t        j                  |d
      d       y Nr   r   Tr   r)   zinsurance-fa-recruitr   r+   r,   r    allow_unicoder&   r'   	r   r   r   r   r   r   
write_textr-   dumpr   	evals_direxisting_datar#   s       r   r   zTestCaptureInputAppend.setUpL   s    &&('%7'A	% -7op

  22	TYY}DIT[\r   c                   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  t        |d         dd	       | j                  |d   d
   d   d       | j                  |d   d   d   d       y)u;   기존 YAML에 새 항목이 리스트 끝에 추가된다.u1   삼성생명 종신보험 신상품 출시 광고r   r   r    r&   r'   r)      u-   기존 1개 + 신규 1개 = 2개이어야 함r   r,   r   r*   N	r   r   r   r   r-   r.   r/   r3   r4   r   r#   r5   s      r   test_appends_to_existing_listz4TestCaptureInputAppend.test_appends_to_existing_list[   s    J{{	
 %7'ADVV	~~i1171CDT(^,a1`ah*624lmh*624ghr   c                   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  |d   d   d	   dd
       y)u5   새 항목이 리스트의 마지막에 추가된다.u   새로운 광고 카피r   r   r    r&   r'   r)   r,   u8   마지막 항목이 새로 추가된 항목이어야 함Nr   r   r   r   r-   r.   r/   r3   rP   s      r   test_new_entry_is_at_endz/TestCaptureInputAppend.test_new_entry_is_at_endj   s    0{{	
 %7'ADVV	~~i1171CDh+F35N  QK  	Lr   Nr8   )r;   r<   r=   r>   r   rQ   rU   r?   r   r   rA   rA   I   s    M]iLr   rA   c                  (    e Zd ZdZddZddZddZy)TestCaptureInputDuplicateu/   테스트 3: 동일 텍스트 중복 → 스킵c                   t        j                         | _        d| _        t	        | j                        | j                  z  dz  }|j                  d       ddddgi}|d	z  }|j                  t        j                  |d
      d       y rC   rG   rJ   s       r   r   zTestCaptureInputDuplicate.setUp{   s    &&('%7'A	% -7op

  22	TYY}DIT[\r   c                    t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  t        |d         dd	       y
)u<   동일 텍스트가 이미 있으면 추가하지 않는다.r   r   r   r    r&   r'   r)   r*   u.   중복 텍스트는 추가하지 않아야 함NrO   rP   s      r   test_duplicate_text_is_skippedz8TestCaptureInputDuplicate.test_duplicate_text_is_skipped   sw    O{{	
 %7'ADVV	~~i1171CDT(^,a1abr   c                j    t        | j                  d| j                        }| j                  |       y)u&   중복 시에도 None을 반환한다.r   r   N)r   r   r   assertIsNoner   results     r   test_duplicate_returns_nonez5TestCaptureInputDuplicate.test_duplicate_returns_none   s-    O{{

 	&!r   Nr8   )r;   r<   r=   r>   r   rZ   r_   r?   r   r   rW   rW   x   s    9]c"r   rW   c                  0    e Zd ZdZddZddZddZddZy)TestCaptureInputFIFOuL   테스트 4: 21번째 입력 → 1번째가 삭제되고 20개 유지 (FIFO)c                f   t        j                         | _        d| _        t	        | j                        | j                  z  dz  }|j                  d       t        dd      D cg c]  }d|dd	|dd
 }}d|i}|dz  }|j                  t        j                  |d      d       y c c}w )Nr   r   Tr   r*      z
input-old-02du   기존 입력 rD   r)   r    rE   r&   r'   )
r   r   r   r   r   r   rangerH   r-   rI   )r   rK   ir)   rL   r#   s         r   r   zTestCaptureInputFIFO.setUp   s    &&('%7'A	% [``aceZfgUV:aW-#w7OPgg!6* 22	TYY}DIT[\ hs   &B.c                    t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  t        |d         dd	       y
)uO   20개가 꽉 찬 상태에서 21번째 추가 시 최대 20개를 유지한다.   21번째 새 입력r   r   r    r&   r'   r)      u    최대 20개를 유지해야 함NrO   rP   s      r   test_max_20_entries_after_21stz3TestCaptureInputFIFO.test_max_20_entries_after_21st   sw    ,{{	
 %7'ADVV	~~i1171CDT(^,b2TUr   c                <   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }|d   D cg c]  }|d   	 }}| j                  d	|d
       yc c}w )u;   FIFO: 가장 오래된 (첫 번째) 항목이 삭제된다.rh   r   r   r    r&   r'   r)   r,   u   기존 입력 01u5   가장 오래된 항목이 삭제되어야 함 (FIFO)N)r   r   r   r   r-   r.   r/   assertNotIn)r   r#   r5   r6   textss        r   test_oldest_entry_removed_fifoz3TestCaptureInputFIFO.test_oldest_entry_removed_fifo   s    ,{{	
 %7'ADVV	~~i1171CD -1N;5v;;+U4kl <s   7Bc                   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }| j                  |d   d   d	   dd
       y)u9   새로 추가된 항목이 리스트 마지막에 있다.rh   r   r   r    r&   r'   r)   rS   r,   u$   새 항목이 마지막이어야 함NrT   rP   s      r   test_newest_entry_is_lastz.TestCaptureInputFIFO.test_newest_entry_is_last   s}    ,{{	
 %7'ADVV	~~i1171CDh+F35JLrsr   Nr8   )r;   r<   r=   r>   r   rj   rn   rp   r?   r   r   ra   ra      s    V
]Vmtr   ra   c                  (    e Zd ZdZddZddZddZy)TestCaptureInputIdFormatu?   테스트 5: id 형식이 "input-YYYYMMDD-HHMMSS" 패턴 준수c                    t        j                         | _        d| _        t	        | j                        | j                  z  }|j                  d       y r
   r   r   s     r   r   zTestCaptureInputIdFormat.setUp   s>    &&('%7	%r   c                *   t        | j                  d| j                         t        | j                        | j                  z  dz  dz  }t	        j
                  |j                  d            }|d   d   d	   }d
}| j                  ||d| d       y)u<   생성된 id가 'input-YYYYMMDD-HHMMSS' 형식을 따른다.   테스트 입력r   r   r    r&   r'   r)   r   r+   z^input-\d{8}-\d{6}$zid 'u0   '가 'input-YYYYMMDD-HHMMSS' 형식이어야 함N)r   r   r   r   r-   r.   r/   assertRegex)r   r#   r5   entry_idpatterns        r   test_id_format_matches_patternz7TestCaptureInputIdFormat.test_id_format_matches_pattern   s    ){{	
 %7'ADVV	~~i1171CD>!$T*(7d8*<l,mnr   c                   ddl m }  |dddddd      }t        d	      5 }||j                  _        t	        | j
                  d
| j                         ddd       t        | j                        | j
                  z  dz  dz  }t        j                  |j                  d            }|d   d   d   }| j                  |d       y# 1 sw Y   txY w)u8   id에 포함된 날짜가 현재 시각과 일치한다.r   )datetimei                 z%scripts.autoresearch.capture.datetimeu   타임스탬프 테스트r   Nr   r    r&   r'   r)   r+   zinput-20260326-143022)r{   r   nowreturn_valuer   r   r   r   r-   r.   r/   r3   )r   r{   
fixed_timemock_dtr#   r5   rw   s          r   test_id_uses_timestampz/TestCaptureInputIdFormat.test_id_uses_timestamp   s    %dAr2r26
:; 	w'1GKK$??6;;	 %7'ADVV	~~i1171CD>!$T*#:;	 	s   4CCNr8   )r;   r<   r=   r>   r   ry   r   r?   r   r   rr   rr      s    I&o<r   rr   c                  (    e Zd ZdZddZddZddZy)TestCaptureInputEvalsDiru7   테스트 6: evals 디렉토리 없으면 자동 생성c                    t        j                         | _        d| _        t	        | j                        | j                  z  }|j                  d       y )Nz	new-skillTr   r   r   s     r   r   zTestCaptureInputEvalsDir.setUp
  s>    &&(%%7	%r   c                   t        | j                        | j                  z  dz  }| j                  |j	                         d       t        | j                  d| j                         | j                  |j	                         d       y)u:   evals 디렉토리가 없으면 자동으로 생성된다.r   u2   사전 조건: evals 디렉토리가 없어야 함ru   r   u0   evals 디렉토리가 자동 생성되어야 함N)r   r   r   assertFalser"   r   r!   )r   rK   s     r   !test_creates_evals_dir_if_missingz:TestCaptureInputEvalsDir.test_creates_evals_dir_if_missing  sk    %7'A	))+-ab){{	
 		((*,^_r   c                $   d}t        | j                        |z  }| j                  |j                         d       t	        |d| j                         t        | j                        |z  dz  dz  }| j                  |j                         d       y)	uB   스킬 디렉토리 자체가 없어도 자동으로 생성된다.zbrand-new-skillu3   사전 조건: 스킬 디렉토리가 없어야 함u   완전 새 스킬의 입력r   r   r    u4   스킬/evals/test-inputs.yaml이 생성되어야 함N)r   r   r   r"   r   r!   )r   r   r   r#   s       r   !test_creates_skill_dir_if_missingz:TestCaptureInputEvalsDir.test_creates_skill_dir_if_missing  s    &
%
2	))+-bc!4{{	
 %
2W<?QQ		((*,bcr   Nr8   )r;   r<   r=   r>   r   r   r   r?   r   r   r   r     s    A&`dr   r   c                  0    e Zd ZdZddZddZddZddZy)!TestCaptureInputExceptionHandlingu=   테스트 7: 예외 발생 시 raise하지 않고 None 반환c                j    t        j                         }t        dd|      }| j                  |       y)u'   정상 동작 시 None을 반환한다.
some-skill	   테스트r   N)r   r   r   r\   )r   r   r^   s      r   test_returns_none_on_successz>TestCaptureInputExceptionHandling.test_returns_none_on_success1  s3    !!##"

 	&!r   c                    t        dt        d            5  	 t        ddd      }| j                  |       d	d	d	       y	# t        $ r | j	                  d       Y %w xY w# 1 sw Y   y	xY w)
uB   파일 I/O 오류가 발생해도 예외를 raise하지 않는다.zbuiltins.openu   디스크 꽉 참side_effectr   r   z/nonexistent/pathr   u$   OSError가 raise되지 않아야 함N)r   OSErrorr   r\   failr]   s     r   test_does_not_raise_on_io_errorzATestCaptureInputExceptionHandling.test_does_not_raise_on_io_error;  sz    ?8K0LM 
	BB&+*2
 !!&)
	B 
	B  B		@AB
	B 
	B(   A!AAA!AA!!A*c                    t        dt        d            5  	 t        ddd      }| j                  |       d	d	d	       y	# t        $ r | j	                  d       Y %w xY w# 1 sw Y   y	xY w)
u>   권한 오류가 발생해도 예외를 raise하지 않는다.zpathlib.Path.mkdiru   권한 없음r   r   r   z/read-only-pathr   u,   PermissionError가 raise되지 않아야 함N)r   PermissionErrorr   r\   r   r]   s     r   'test_does_not_raise_on_permission_errorzITestCaptureInputExceptionHandling.test_does_not_raise_on_permission_errorI  sz    '__5UV 		JJ&+*0
 !!&)		J 		J # J		HIJ		J 		Jr   c                &   t        j                         }d}t        |      |z  dz  }|j                  d       |dz  }|j	                  dd       	 t        |d	|
      }| j                  |       y# t        $ r | j                  d       Y yw xY w)uJ   기존 YAML 파싱 오류가 발생해도 예외를 raise하지 않는다.zbroken-skillr   Tr   r    z!invalid: yaml: content: [unclosedr&   r'   r   r   u:   YAML 파싱 오류 시 예외가 raise되지 않아야 함N)	r   r   r   r   rH   r   r\   	Exceptionr   )r   r   r   rK   r#   r^   s         r   'test_does_not_raise_on_yaml_parse_errorzITestCaptureInputExceptionHandling.test_does_not_raise_on_yaml_parse_errorV  s    !!##
L:-7	%  22	@7S	T"%&!F
 f% 	TIIRS	Ts   A3 3BBNr8   )r;   r<   r=   r>   r   r   r   r   r?   r   r   r   r   .  s    G"BJTr   r   __main__)r>   
__future__r   resysr   unittestpathlibr   unittest.mockr   r-   str__file__resolveparent_WORKSPACE_ROOTpathinsertscripts.autoresearch.capturer   TestCaser   rA   rW   ra   rr   r   r   r;   mainr?   r   r   <module>r      s  	 # 	 
      d8n,,.55<<CCJJK#(("HHOOA' 6'bX.. 'bT,LX.. ,L^%" 1 1 %"P6t8,, 6tr+<x00 +<\$dx00 $dN;T(9(9 ;T| zHMMO r   