
    iF                     8   d Z ddlZddlZddlmZmZmZ ddlmZ ddlZej                  j                  d e ee      j                  j                               ddl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 for learnings-archiver.py - TDD approach (RED phase first).

테스트 케이스:
1. add_learning → 파일에 올바른 형식으로 추가되는지
2. get_learnings → 해당 스킬만 반환, 다른 스킬 learning 미포함
3. archive_expired → 만료 항목 이동 + 원본에서 제거 + archive에 존재
4. jay-feedback → expires_at null → TTL 아카이브에서 제외
5. 잘못된 source → 에러 발생
6. 빈 파일에서 get_learnings → 빈 리스트 반환
    N)datetime	timedeltatimezone)Pathc                   P    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZy)	TestAddLearninguO   테스트 1: add_learning → 파일에 올바른 형식으로 추가되는지.tmp_pathreturnNc                    |dz  }|dz  }t        j                  ddd||       |j                         sJ |j                  d      j	                         j                         }t        |      d	k(  sJ t        j                  |d
         }d|v sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ d|v sJ d|v sJ t        j                  |d         }t        j                  |d         }||z
  }d|j                  cxk  rdk  sJ  J y)uO   add_learning 호출 시 learnings.jsonl에 올바른 형식으로 추가된다.learnings.jsonllearnings-archive.jsonlsatori-cardnewschampion-battleu`   CTA를 마지막 슬라이드가 아닌 3번째에 배치하면 시선 흐름이 자연스럽다
skill_namesourcelearninglearnings_patharchive_pathutf-8encoding   r   idr   r   r   
created_at
expires_at;   =   N)laadd_learningexists	read_textstrip
splitlineslenjsonloadsr   fromisoformatdays)	selfr	   learnings_filearchive_filelinesentrycreatedexpiresdeltas	            V/home/jay/workspace/.worktrees/task-2116-dev1/scripts/tests/test_learnings_archiver.py+test_add_learning_creates_valid_jsonl_entryz;TestAddLearning.test_add_learning_creates_valid_jsonl_entry   sN   !$55";;
($w)%	
 $$&&&(('(:@@BMMO5zQ

58$u}}\"&7777X"3333Z   %G  G  	G  Gu$$$u$$$((|)<=((|)<='!UZZ%2%%%%%    c                     |dz  }|dz  }t        j                  ddd||       t        j                  ddd	||       |j                  d
      j                         j	                         }t        |      dk(  sJ y)uD   add_learning을 여러 번 호출하면 append-only로 추가된다.r   r   skill-aself-reviewu   첫 번째 학습r   skill-bcross-modelu   두 번째 학습r   r      Nr   r    r"   r#   r$   r%   )r*   r	   r+   r,   r-   s        r2   *test_add_learning_appends_multiple_entriesz:TestAddLearning.test_add_learning_appends_multiple_entries:   s    !$55";;
  ()%	
 	  ()%	
 (('(:@@BMMO5zQr4   c                     |dz  }|dz  }t        j                  ddd||       |j                  d      j                         j	                         }t        j                  |d	         }|d
   J y)u@   source가 jay-feedback이면 expires_at이 null이어야 한다.r   r   r   jay-feedbacku   jay의 중요 피드백r   r   r   r   r   N)r   r    r"   r#   r$   r&   r'   )r*   r	   r+   r,   r-   r.   s         r2   2test_add_learning_jay_feedback_has_null_expires_atzBTestAddLearning.test_add_learning_jay_feedback_has_null_expires_atQ   s}    !$55";;
(!.)%	
 (('(:@@BMMO

58$\"***r4   c                 Z   |dz  }|dz  }t        j                  ddd||       |j                  d      j                         j	                         }t        j                  |d	         }|d
   }|j                  d      }t        |      dk(  xr |j                  d      dk(  }|s|sJ yy)u3   id는 learn-NNN 또는 UUID 형식이어야 한다.r   r   
test-skillgithub-learn   테스트 학습r   r   r   r   r   learn-$   -   N)
r   r    r"   r#   r$   r&   r'   
startswithr%   count)	r*   r	   r+   r,   r-   r.   entry_idis_learn_formatis_uuid_formats	            r2   test_add_learning_id_formatz+TestAddLearning.test_add_learning_id_formatb   s    !$55";;
#!')%	
 (('(:@@BMMO

58$;"--h7X",I1D1I.00.r4   )	__name__
__module____qualname____doc__r   r3   r<   r?   rM    r4   r2   r   r      sR    Y&D &T &<4 D .+4 +TX +"1D 1T 1r4   r   c                   @    e Zd ZdZdeddfdZdeddfdZdeddfdZy)TestGetLearningsuY   테스트 2: get_learnings → 해당 스킬만 반환, 다른 스킬 learning 미포함.r	   r
   Nc           	         |dz  }t        j                         }|t        d      z   j                         }|t        d      z
  j                         }dddd|j                         |d	d
ddd|j                         |d	dddd|j                         |d	g}|j	                  dj                  d |D              d       t        j                  d|      }t        |      dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ y)uC   get_learnings는 지정한 스킬의 활성 항목만 반환한다.r      r)   r   	learn-001r6   r7   u   skill-a의 학습r   r   r   r   r   r   	learn-002r8   u!   skill-b의 학습 (다른 스킬)z	learn-003r9   u   skill-a의 만료된 학습
c              3   F   K   | ]  }t        j                  |        y wNr&   dumps.0es     r2   	<genexpr>zPTestGetLearnings.test_get_learnings_returns_only_target_skill.<locals>.<genexpr>        +KaDJJqM+K   !r   r   r   r   r   r   N	r   nowr   	isoformat
write_textjoinr   get_learningsr%   )r*   r	   r+   rh   futurepastentriesresults           r2   ,test_get_learnings_returns_only_target_skillz=TestGetLearnings.test_get_learnings_returns_only_target_skill{   s$   !$55lln	r**557iQ''224 "''/!mmo$ "''?!mmo$ "''9!mmo"#
4 	!!$))+K7+K"KV]!^!!)NK 6{aay+---ay&)333r4   c                 @   |dz  }t        j                         }|t        d      z   j                         }dddd|j                         |dg}|j	                  d	j                  d
 |D              d       t        j                  d|      }t        |      dk(  sJ y)uL   get_learnings는 다른 스킬의 learning을 절대 포함하지 않는다.r   rV   rW   rX   r8   r7   u   다른 스킬의 학습rY   r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   zLTestGetLearnings.test_get_learnings_excludes_other_skills.<locals>.<genexpr>   rd   re   r   r   r6   rf   r   Nrg   )r*   r	   r+   rh   rm   ro   rp   s          r2   (test_get_learnings_excludes_other_skillsz9TestGetLearnings.test_get_learnings_excludes_other_skills   s    !$55lln	r**557 "''5!mmo$	
 	!!$))+K7+K"KV]!^!!)NK6{ar4   c                    |dz  }t        j                         }dddd|j                         ddg}|j                  dj	                  d	 |D              d
       t        j                  d|      }t        |      dk(  sJ |d   d   J y)uA   expires_at이 null인 항목은 항상 활성으로 반환한다.r   rX   r6   r>   u   영구 유지 학습NrY   r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   zOTestGetLearnings.test_get_learnings_includes_null_expires_at.<locals>.<genexpr>   rd   re   r   r   rf   r   r   r   )r   rh   ri   rj   rk   r   rl   r%   )r*   r	   r+   rh   ro   rp   s         r2   +test_get_learnings_includes_null_expires_atz<TestGetLearnings.test_get_learnings_includes_null_expires_at   s    !$55lln "'(2!mmo"	
 	!!$))+K7+K"KV]!^!!)NK6{aay&...r4   )rN   rO   rP   rQ   r   rq   rt   rw   rR   r4   r2   rT   rT   x   s?    c(4T (4d (4T   $  ,/D /T /r4   rT   c                   @    e Zd ZdZdeddfdZdeddfdZdeddfdZy)TestArchiveExpiredu`   테스트 3: archive_expired → 만료 항목 이동 + 원본에서 제거 + archive에 존재.r	   r
   Nc                 V   |dz  }|dz  }t        j                         }|t        d      z   j                         }|t        d      z
  j                         }dddd	|j                         |d
dddd|j                         |d
g}|j	                  dj                  d |D              d       t        j                  ||      }|dk(  sJ |j                         j                         j                         D 	cg c]  }	t        j                  |	       }
}	t        |
      dk(  sJ |
d   d   dk(  sJ |j                         j                         j                         D 	cg c]  }	t        j                  |	       }}	t        |      dk(  sJ |d   d   dk(  sJ yc c}	w c c}	w )uY   archive_expired는 만료된 항목을 archive로 이동하고 원본에서 제거한다.r   r   rV   rW   r   rX   r6   r7   u   활성 학습rY   rZ   r9   u   만료된 학습r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   zPTestArchiveExpired.test_archive_expired_moves_expired_entries.<locals>.<genexpr>   rd   re   r   r   r   r   r   r   N)r   rh   r   ri   rj   rk   r   archive_expiredr"   r#   r$   r&   r'   r%   )r*   r	   r+   r,   rh   rm   rn   ro   rI   line	remainingarchiveds               r2   *test_archive_expired_moves_expired_entriesz=TestArchiveExpired.test_archive_expired_moves_expired_entries   s   !$55";;lln	r**557iQ''224 "''+!mmo$ "''.!mmo"
$ 	!!$))+K7+K"KV]!^"".|\zz 3A2J2J2L2R2R2T2_2_2ab$TZZ%b	b9~"""|D![000 2>1G1G1I1O1O1Q1\1\1^_DJJt$__8}!!!{4 K/// c
 `s   <F!%F&c           	         |dz  }|dz  }t        j                         }|t        d      z
  j                         }dddd|j                         |t        d	      z
  j                         d
}|j	                  t        j                  |      dz   d       dddd|j                         |d
}|j	                  t        j                  |      dz   d       t        j                  ||       |j                         j                         j                         D cg c]  }t        j                  |       }	}t        |	      dk(  sJ |	D 
cg c]  }
|
d   	 }}
d|v sJ d|v sJ yc c}w c c}
w )uK   archive_expired는 기존 archive에 append한다 (덮어쓰지 않는다).r   r   r   rW   z	learn-oldr8   r7   u   기존 아카이브 항목
   rY   r[   r   r   z	learn-newr6   r9   u   새 만료 항목r|   r:   r   N)r   rh   r   ri   rj   r&   r_   r   r}   r"   r#   r$   r'   r%   )r*   r	   r+   r,   rh   rn   existing_archivednew_expiredr~   r   aidss               r2   0test_archive_expired_appends_to_existing_archivezCTestArchiveExpired.test_archive_expired_appends_to_existing_archive   sm   !$55";;llniQ''224 ##4--/!33>>@
 	

+< = DwW ##+--/
 	!!$**["9D"@7!S
.|T1=1G1G1I1O1O1Q1\1\1^_DJJt$__8}!!! ()1qw))c!!!c!!!	 `)s   E)E.c           
      z   |dz  }|dz  }t        j                         }|t        d      z
  j                         }t	        d      D cg c]   }d|ddd	d
| |j                         |d" }}|j                  dj                  d |D              d       t        j                  ||      }|dk(  sJ yc c}w )u>   archive_expired는 아카이브된 항목 수를 반환한다.r   r   r   rW      rD   03dr6   r7   u   만료 학습 rY   r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   zHTestArchiveExpired.test_archive_expired_returns_count.<locals>.<genexpr>6  rd   re   r   r   r|   N)	r   rh   r   ri   rangerj   rk   r   r}   )	r*   r	   r+   r,   rh   rn   iro   rI   s	            r2   "test_archive_expired_returns_countz5TestArchiveExpired.test_archive_expired_returns_count$  s    !$55";;llniQ''224 1X

  qg&'',QC0!mmo"

 

 	!!$))+K7+K"KV]!^"".|\zz

s   	%B8)rN   rO   rP   rQ   r   r   r   r   rR   r4   r2   ry   ry      sA    j(04 (0D (0T#" #"RV #"J4 D r4   ry   c                   0    e Zd ZdZdeddfdZdeddfdZy)TestJayFeedbackTTLExclusionuP   테스트 4: jay-feedback → expires_at null → TTL 아카이브에서 제외.r	   r
   Nc                 L   |dz  }|dz  }t        j                         }|t        d      z
  j                         }dddd|j                         d	d
dddd|j                         |d
g}|j	                  dj                  d |D              d       t        j                  ||      }|dk(  sJ |j                         j                         j                         D cg c]  }t        j                  |       }	}|	D 
cg c]  }
|
d   	 }}
d|v sJ d|vsJ y	c c}w c c}
w )uS   source가 jay-feedback이고 expires_at이 null이면 아카이브하지 않는다.r   r   r   rW   rX   r6   r>   u   jay의 영구 피드백NrY   rZ   r7   u   만료된 일반 학습r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   zbTestJayFeedbackTTLExclusion.test_jay_feedback_with_null_expires_at_not_archived.<locals>.<genexpr>Y  rd   re   r   r   r|   r   )r   rh   r   ri   rj   rk   r   r}   r"   r#   r$   r&   r'   )r*   r	   r+   r,   rh   rn   ro   rI   r~   r   rremaining_idss               r2   3test_jay_feedback_with_null_expires_at_not_archivedzOTestJayFeedbackTTLExclusion.test_jay_feedback_with_null_expires_at_not_archived@  s8   !$55";;llniQ''224 "'(5!mmo" "''5!mmo"
$ 	!!$))+K7+K"KV]!^"".|\ zz2@2J2J2L2R2R2T2_2_2ab$TZZ%b	b*34Q444m+++-/// c4s   DD!c                 8   |dz  }|dz  }t        j                         }|t        d      z
  j                         }dddd|j                         |d	g}|j	                  d
j                  d |D              d       t        j                  ||      }|dk(  sJ y)uu   source가 jay-feedback이면 expires_at이 명시적으로 설정되어 만료되어도 아카이브하지 않는다.r   r   r   rW   rX   r6   r>   u   만료된 jay 피드백rY   r[   c              3   F   K   | ]  }t        j                  |        y wr]   r^   r`   s     r2   rc   z_TestJayFeedbackTTLExclusion.test_jay_feedback_with_expired_date_NOT_archived.<locals>.<genexpr>v  rd   re   r   r   r|   r   N)r   rh   r   ri   rj   rk   r   r}   )r*   r	   r+   r,   rh   rn   ro   rI   s           r2   0test_jay_feedback_with_expired_date_NOT_archivedzLTestJayFeedbackTTLExclusion.test_jay_feedback_with_expired_date_NOT_archivede  s    !$55";;llniQ''224 "'(5!mmo"	
 	!!$))+K7+K"KV]!^"".|\ zzr4   )rN   rO   rP   rQ   r   r   r   rR   r4   r2   r   r   =  s.    Z#0D #0UY #0J RV r4   r   c                   0    e Zd ZdZdeddfdZdeddfdZy)TestInvalidSourceu0   테스트 5: 잘못된 source → 에러 발생.r	   r
   Nc                     |dz  }|dz  }t        j                  t        d      5  t        j                  ddd||       d	d	d	       y	# 1 sw Y   y	xY w)
uQ   유효하지 않은 source로 add_learning 호출 시 ValueError가 발생한다.r   r   zInvalid source)matchrA   zinvalid-sourcerC   r   N)pytestraises
ValueErrorr   r    )r*   r	   r+   r,   s       r2   -test_add_learning_invalid_source_raises_errorz?TestInvalidSource.test_add_learning_invalid_source_raises_error  sW    !$55";;]]:-=> 	OO''+-)	 	 	s   A

Ac                     |dz  }|dz  }g d}|D ]  }t        j                  d|| d||       ! |j                  d      j                         j	                         }t        |      t        |      k(  sJ y	)
u=   유효한 source 목록은 모두 에러 없이 추가된다.r   r   )r7   r9   r>   zonline-expertrB   r   rA   u    학습r   r   r   Nr;   )r*   r	   r+   r,   valid_sourcesr   r-   s          r2   ,test_add_learning_valid_sources_do_not_raisez>TestInvalidSource.test_add_learning_valid_sources_do_not_raise  s    !$55";;
 $ 	FOO'"87+-)	 (('(:@@BMMO5zS////r4   )rN   rO   rP   rQ   r   r   r   rR   r4   r2   r   r   ~  s+    :d t 0T 0d 0r4   r   c                   @    e Zd ZdZdeddfdZdeddfdZdeddfdZy)TestEmptyFileGetLearningsuE   테스트 6: 빈 파일에서 get_learnings → 빈 리스트 반환.r	   r
   Nc                 p    |dz  }|j                  dd       t        j                  d|      }|g k(  sJ y)uR   learnings.jsonl이 비어있으면 get_learnings는 빈 리스트를 반환한다.r    r   r   r6   rf   N)rj   r   rl   r*   r	   r+   rp   s       r2   5test_get_learnings_from_empty_file_returns_empty_listzOTestEmptyFileGetLearnings.test_get_learnings_from_empty_file_returns_empty_list  s>    !$55!!"w!7!!)NK||r4   c                 J    |dz  }t        j                  d|      }|g k(  sJ y)uY   learnings.jsonl이 존재하지 않으면 get_learnings는 빈 리스트를 반환한다.r   r6   rf   N)r   rl   r   s       r2   ;test_get_learnings_from_nonexistent_file_returns_empty_listzUTestEmptyFileGetLearnings.test_get_learnings_from_nonexistent_file_returns_empty_list  s+    !$55!!)NK||r4   c                 z    |dz  }|dz  }|j                  dd       t        j                  ||      }|dk(  sJ y)	uH   learnings.jsonl이 비어있으면 archive_expired는 0을 반환한다.r   r   r   r   r   r|   r   N)rj   r   r}   )r*   r	   r+   r,   rI   s        r2   1test_archive_expired_from_empty_file_returns_zerozKTestEmptyFileGetLearnings.test_archive_expired_from_empty_file_returns_zero  sI    !$55";;!!"w!7"".|\zzr4   )rN   rO   rP   rQ   r   r   r   r   rR   r4   r2   r   r     sB    Od W[ TX ]a $ SW r4   r   )rQ   r&   sysr   r   r   pathlibr   r   pathinsertstr__file__parentlearnings_archiverr   r   rT   ry   r   r   r   rR   r4   r2   <module>r      s   	  
 2 2   3tH~,,334 5 \1 \1~W/ W/th hV> >B)0 )0X r4   