
    (<iJF                       d Z ddlmZ ddlmZ ddlZddlmZmZm	Z	m
Z
mZmZmZmZmZmZmZmZmZmZ dZ ej,                  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  skill_loader.py Progressive Disclosure 기능 단위 테스트.

테스트 대상:
- SkillLevel enum (CORE, STANDARD, EXTENDED)
- classify_skill(name)
- match_skills_by_prompt(prompt, skills_dir)
- get_skills_by_level(skills_dir, level)
- check_security_whitelist(skills_dir)
- set_progressive_disclosure / is_progressive_disclosure_enabled
- list_skills, view_skill, load_skill (회귀 테스트)
    )annotations)PathN)KEYWORD_TRIGGERSSECURITY_WHITELISTSkillDetail
SkillLevelSkillSummarycheck_security_whitelistclassify_skillget_skills_by_level!is_progressive_disclosure_enabledlist_skills
load_skillmatch_skills_by_promptset_progressive_disclosure
view_skillz/home/jay/workspace/skillsT)autousec               #  *   K   d t        d       yw)u[   각 테스트가 끝난 뒤 Progressive Disclosure flag를 True(기본값)로 복원한다.NT)r        H/home/jay/workspace/.worktrees/task-2057-dev2/tests/test_skill_loader.pyrestore_progressive_disclosurer   ,   s      
t$s   c                  4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestClassifySkilluK   classify_skill 함수의 CORE / STANDARD / EXTENDED 분류를 검증한다.c                r    t         D ].  }t        |      }|t        j                  k(  r"J d| d| d        y)uI   SECURITY_WHITELIST에 포함된 스킬은 CORE로 분류되어야 한다.'u   ' 은 CORE여야 하지만     로 분류됨N)r   r   r   COREself
skill_nameresults      r   'test_security_whitelist_skills_are_corez9TestClassifySkill.test_security_whitelist_skills_are_core;   sG    , 	pJ#J/FZ__,o*=XY_X``n.oo,	pr   c                r    t         D ].  }t        |      }|t        j                  k(  r"J d| d| d        y)uK   KEYWORD_TRIGGERS에 포함된 스킬은 STANDARD로 분류되어야 한다.r   u   ' 은 STANDARD여야 하지만 r   N)r   r   r   STANDARDr   s      r   (test_keyword_trigger_skills_are_standardz:TestClassifySkill.test_keyword_trigger_skills_are_standardA   sI    * 	xJ#J/FZ000wAj\A`ag`hhv2ww0	xr   c                r    g d}|D ].  }t        |      }|t        j                  k(  r"J d| d| d        y)u7   미등록 스킬은 EXTENDED로 분류되어야 한다.)totally-unknown-skillzmy-custom-skillznot-a-real-skill-xyzr   u   ' 은 EXTENDED여야 하지만 r   N)r   r   EXTENDED)r    unregistered_namesnamer"   s       r   #test_unregistered_skill_is_extendedz5TestClassifySkill.test_unregistered_skill_is_extendedG   sS    

 ' 	rD#D)FZ000qAdV;Z[aZbbp2qq0	rr   c                @    t        d      t        j                  k(  sJ y)uF   blog-writer는 KEYWORD_TRIGGERS에 있으므로 STANDARD여야 한다.blog-writerNr   r   r%   r    s    r   test_blog_writer_is_standardz.TestClassifySkill.test_blog_writer_is_standardR   s    m,
0C0CCCCr   c                @    t        d      t        j                  k(  sJ y)uI   nuclear-approval은 SECURITY_WHITELIST에 있으므로 CORE여야 한다.nuclear-approvalN)r   r   r   r0   s    r   test_nuclear_approval_is_corez/TestClassifySkill.test_nuclear_approval_is_coreV   s    01Z__DDDr   c                @    t        d      t        j                  k(  sJ y)u@   retro는 KEYWORD_TRIGGERS에 있으므로 STANDARD여야 한다.retroNr/   r0   s    r   test_retro_is_standardz(TestClassifySkill.test_retro_is_standardZ   s    g&**=*====r   N)
__name__
__module____qualname____doc__r#   r&   r,   r1   r4   r7   r   r   r   r   r   8   s(    Upx	rDE>r   r   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestFeatureFlagu;   Progressive Disclosure feature flag 동작을 검증한다.c                6    t        d       t               du sJ y)u6   기본 상태에서 feature flag가 True여야 한다.TNr   r   r0   s    r   test_flag_default_is_enabledz,TestFeatureFlag.test_flag_default_is_enabledg   s    "4(02d:::r   c                
   t        d       t        d      t        j                  k(  sJ t        d      t        j                  k(  sJ t        d      t        j                  k(  sJ t        d      t        j                  k(  sJ y)uB   flag가 OFF이면 모든 스킬이 CORE로 분류되어야 한다.Fr.   r6   r(   r3   N)r   r   r   r   r0   s    r   $test_flag_off_all_skills_become_corez4TestFeatureFlag.test_flag_off_all_skills_become_corel   sh    "5)m,
???g&*//99956*//III01Z__DDDr   c                    t        d       t        d      t        j                  k(  sJ t        d      t        j                  k(  sJ t        d      t        j
                  k(  sJ y)uG   flag가 ON이면 정상적인 3단계 분류가 이루어져야 한다.Tr3   r.   zunknown-xyzN)r   r   r   r   r%   r)   r0   s    r   "test_flag_on_normal_classificationz2TestFeatureFlag.test_flag_on_normal_classificationu   sT    "4(01Z__DDDm,
0C0CCCCm,
0C0CCCCr   c                    t        d       t        d      t        j                  k(  sJ t        d       t        d      t        j                  k(  sJ y)uR   flag를 OFF→ON으로 토글하면 분류가 원래대로 복원되어야 한다.Fr.   TN)r   r   r   r   r%   r0   s    r   (test_flag_toggle_restores_classificationz8TestFeatureFlag.test_flag_toggle_restores_classification}   sB    "5)m,
???"4(m,
0C0CCCCr   c                6    t        d       t               du sJ y)uf   autouse fixture가 flag를 True로 복원하는지 검증하기 위해 OFF로 설정 후 확인한다.FNr?   r0   s    r    test_flag_is_restored_after_testz0TestFeatureFlag.test_flag_is_restored_after_test   s    "5)02e;;;r   N)	r8   r9   r:   r;   r@   rB   rD   rF   rH   r   r   r   r=   r=   d   s!    E;
EDD<r   r=   c                  @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestMatchSkillsByPromptu[   match_skills_by_prompt가 프롬프트 키워드를 정확히 매칭하는지 검증한다.c                |    t        dt              }|D cg c]  }|j                   }}d|v s
J d|        yc c}w )uV   '블로그' 키워드가 포함된 프롬프트는 blog-writer를 매칭해야 한다.u   블로그 글 써줘r.   uV   '블로그 글 써줘' 프롬프트에서 blog-writer가 매칭되지 않음. 결과: Nr   
SKILLS_DIRr+   r    resultssmatched_namess       r   'test_korean_keyword_matches_blog_writerz?TestMatchSkillsByPrompt.test_korean_keyword_matches_blog_writer   sN    ()?L)01A11]*	tcdqcrs	t* 2   9c                |    t        dt              }|D cg c]  }|j                   }}d|v s
J d|        yc c}w )uO   'pdf' 키워드가 포함된 영어 프롬프트는 pdf를 매칭해야 한다.zhelp with PDFpdfuG   'help with PDF' 프롬프트에서 pdf가 매칭되지 않음. 결과: NrL   rN   s       r    test_english_keyword_matches_pdfz8TestMatchSkillsByPrompt.test_english_keyword_matches_pdf   sP    (*E)01A11%  	A)pq~p  (A  	A% 2rS   c                |    t        dt              }|D cg c]  }|j                   }}d|v s
J d|        yc c}w )ua   대소문자를 무시하고 'SEO'가 포함된 프롬프트는 seo-audit을 매칭해야 한다.u   SEO audit 해줘	seo-audituP   'SEO audit 해줘' 프롬프트에서 seo-audit이 매칭되지 않음. 결과: NrL   rN   s       r   test_case_insensitive_seo_auditz7TestMatchSkillsByPrompt.test_case_insensitive_seo_audit   sN    ();ZH)01A11=(	n]^k]lm	n( 2rS   c                B    t        dt              }|g k(  s
J d|        y)uI   매칭되는 키워드가 없으면 빈 리스트를 반환해야 한다.u+   아무 키워드도 없는 일반 텍스트u3   매칭 없을 때 빈 리스트여야 함. 결과: N)r   rM   )r    rO   s     r   "test_no_keyword_returns_empty_listz:TestMatchSkillsByPrompt.test_no_keyword_returns_empty_list   s+    ()VXbc"}] ST[S\]]}r   c                    t        dt              }|D cg c]  }|j                   }}d|v s
J d|        d|v s
J d|        yc c}w )ua   '블로그 SEO 전략' 프롬프트는 blog-writer와 seo-audit을 동시에 매칭해야 한다.u   블로그 SEO 전략r.   uI   '블로그 SEO 전략'에서 blog-writer가 매칭되지 않음. 결과: rX   uG   '블로그 SEO 전략'에서 seo-audit이 매칭되지 않음. 결과: NrL   rN   s       r   )test_multiple_skills_match_simultaneouslyzATestMatchSkillsByPrompt.test_multiple_skills_match_simultaneously   s|    ()?L)01A11]*	gVWdVef	g*m+  	G/v  xE  wF  .G  	G+	 2s   Ac                    t        dt              }t        |t              sJ |D ]%  }t        |t              rJ dt        |               y)u1   반환 타입이 list[SkillSummary]여야 한다.u
   pdf 변환u)   결과 항목이 SkillSummary가 아님: N)r   rM   
isinstancelistr	   type)r    rO   items      r   )test_result_type_is_list_of_skill_summaryzATestMatchSkillsByPrompt.test_result_type_is_list_of_skill_summary   sT    (zB'4((( 	lDdL1k5^_cdh_i^j3kk1	lr   c                |    t        dt              }|D cg c]  }|j                   }}d|v s
J d|        yc c}w )uM   '회고' 키워드가 포함된 프롬프트는 retro를 매칭해야 한다.u   이번 주 회고 작성해줘r6   uM   '이번 주 회고 작성해줘'에서 retro가 매칭되지 않음. 결과: NrL   rN   s       r   test_retro_keyword_matchz0TestMatchSkillsByPrompt.test_retro_keyword_match   sY    ()I:V)01A11-'  	I+x  zG  yH  *I  	I' 2rS   c                l    t        dt              }|D cg c]  }|j                   }}d|v sJ yc c}w )u?   소문자 'pdf' 키워드도 pdf 스킬을 매칭해야 한다.u   pdf 파일 만들어줘rU   NrL   rN   s       r   test_lowercase_pdf_keywordz2TestMatchSkillsByPrompt.test_lowercase_pdf_keyword   s9    ()BJO)01A11%%% 2s   1N)r8   r9   r:   r;   rR   rV   rY   r[   r]   rc   re   rg   r   r   r   rJ   rJ      s4    etAn^
GlI&r   rJ   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestCheckSecurityWhitelistuh   check_security_whitelist가 보안 관련 미등록 스킬을 올바르게 감지하는지 검증한다.c                h    t        t              }t        |t              sJ dt	        |              y)u(   반환 타입이 list[str]여야 한다.u)   반환 타입이 list여야 함. 실제: N)r
   rM   r_   r`   ra   r    r"   s     r   test_returns_list_typez1TestCheckSecurityWhitelist.test_returns_list_type   s1    )*5&$'c+TUYZ`UaTb)cc'r   c                v    t        t              }|D ]%  }t        |t              rJ dt	        |               y)u?   반환된 목록의 모든 항목이 문자열이어야 한다.u+   목록 항목이 str이어야 함. 실제: N)r
   rM   r_   strra   r    r"   rb   s      r   test_all_items_are_stringsz5TestCheckSecurityWhitelist.test_all_items_are_strings   sB    )*5 	eDdC(d,WX\]aXbWc*dd(	er   c                V    t        t              }t        D ]  }||vrJ d| d        y)uY   SECURITY_WHITELIST에 등록된 스킬은 경고 목록에 포함되지 않아야 한다.u   화이트리스트 스킬 'u   '이 경고 목록에 포함됨N)r
   rM   r   )r    r"   r!   s      r   'test_whitelisted_skills_not_in_warningszBTestCheckSecurityWhitelist.test_whitelisted_skills_not_in_warnings   s;    )*5, 	wJV+v/J:,Vu-vv+	wr   c                (    t        d      }|g k(  sJ y)uI   존재하지 않는 디렉토리는 빈 리스트를 반환해야 한다.z/nonexistent/path/to/skillsN)r
   rk   s     r   'test_nonexistent_dir_returns_empty_listzBTestCheckSecurityWhitelist.test_nonexistent_dir_returns_empty_list   s    )*GH||r   N)r8   r9   r:   r;   rl   rp   rr   rt   r   r   r   ri   ri      s    rd
ewr   ri   c                  4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestGetSkillsByLeveluU   get_skills_by_level이 레벨별 스킬을 올바르게 반환하는지 검증한다.c                N   t        t        t        j                        }|D cg c]  }|j                   }}t
        D cg c](  }t        t              |z  dz  j                         s'|* }}t        |      dkD  sJ d       |D ]  }||v rJ d| d|         yc c}w c c}w )u~   CORE 레벨 조회 시 SECURITY_WHITELIST에 있는 스킬(skills 디렉토리에 존재하는 것)이 포함되어야 한다.SKILL.mdr   uK   테스트 전제: SECURITY_WHITELIST 스킬이 1개 이상 존재해야 함u   CORE 레벨에 'u.   '이 포함되어야 함. CORE 스킬 목록: N)	r   rM   r   r   r+   r   r   existslen)r    core_skillsrP   
core_namesr+   existing_whitelistr!   s          r   2test_core_level_includes_security_whitelist_skillszGTestGetSkillsByLevel.test_core_level_includes_security_whitelist_skills   s    )*jooF&12aff2
2 0Butd:FVY]F]`jFjErErEtduu%&*y,yy*, 	GJ+  G/?
|Ky  {E  zF  .G  G+	G 3 vs   B (B")B"c                    t        t        t        j                        }t	        |t
              sJ |D ]  }t	        |t              rJ  y)u=   CORE 레벨 조회 결과가 list[SkillSummary]여야 한다.N)r   rM   r   r   r_   r`   r	   ro   s      r   -test_core_level_returns_list_of_skill_summaryzBTestGetSkillsByLevel.test_core_level_returns_list_of_skill_summary   sA    $ZA&$''' 	2DdL111	2r   c                J   t        t        t        j                        }|D cg c]  }|j                   }}t
        D cg c](  }t        t              |z  dz  j                         s'|* }}t        |      dkD  sJ d       |D ]  }||v rJ d| d        yc c}w c c}w )uq   STANDARD 레벨에는 KEYWORD_TRIGGERS 스킬 중 skills 디렉토리에 있는 것들이 포함되어야 한다.rx   r   uI   테스트 전제: KEYWORD_TRIGGERS 스킬이 1개 이상 존재해야 함u   STANDARD 레벨에 'u   '이 포함되어야 함.N)	r   rM   r   r%   r+   r   r   ry   rz   )r    standard_skillsrP   standard_namesr+   existing_standardr!   s          r   3test_standard_level_includes_keyword_trigger_skillszHTestGetSkillsByLevel.test_standard_level_includes_keyword_trigger_skills  s    -j*:M:MN*9:Q!&&::.>rd4
CSVZCZ]gCgBoBoBqTrr$%)v+vv)+ 	nJ/m3G
|Sl1mm/	n ;rs   B (B )B c                    t        t        t        j                        }|D ]P  }|j                  t
        vsJ d|j                   d       |j                  t        vr=J d|j                   d        y)ue   EXTENDED 레벨에는 SECURITY_WHITELIST나 KEYWORD_TRIGGERS에 포함된 스킬이 없어야 한다.u.   EXTENDED 레벨에 SECURITY_WHITELIST 스킬 'u   '이 포함됨u,   EXTENDED 레벨에 KEYWORD_TRIGGERS 스킬 'N)r   rM   r   r)   r+   r   r   )r    extended_skillsskills      r   3test_extended_level_excludes_whitelist_and_triggerszHTestGetSkillsByLevel.test_extended_level_excludes_whitelist_and_triggers  s    -j*:M:MN$ 	AE

"44[?

|>Z[4::%55  A9efkfpfpeqq  8A  A5		Ar   c                    t        d       t        t              }t        t        t        j
                        }t        |      t        |      k(  s J dt        |       dt        |       d       y)uT   flag가 OFF이면 get_skills_by_level(CORE)이 모든 스킬을 반환해야 한다.Fu   flag OFF 시 CORE 스킬 수(u   )가 전체 스킬 수(u   )와 같아야 함N)r   r   rM   r   r   r   rz   )r    
all_skillsr{   s      r    test_flag_off_all_skills_in_corez5TestGetSkillsByLevel.test_flag_off_all_skills_in_core  sn    "5) ,
)*jooF;3$
 
 	x*3{+;*<<STWXbTcSddvw	x 
r   c                    t        d       t        t        t        j                        g k(  sJ t        t        t        j
                        g k(  sJ y)uV   flag가 OFF이면 STANDARD와 EXTENDED 레벨은 빈 리스트를 반환해야 한다.FN)r   r   rM   r   r%   r)   r0   s    r   )test_flag_off_standard_and_extended_emptyz>TestGetSkillsByLevel.test_flag_off_standard_and_extended_empty#  s@    "5)":z/B/BCrIII":z/B/BCrIIIr   N)
r8   r9   r:   r;   r~   r   r   r   r   r   r   r   r   rv   rv      s(    _
G2	nAxJr   rv   c                  X    e 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d Zd Zy)TestLegacyFunctionsuE   list_skills, view_skill, load_skill의 기존 동작을 검증한다.c                h    t        t              }t        |t              sJ dt	        |              y)u8   list_skills 호출 시 결과가 리스트여야 한다.u.   list_skills 결과가 list여야 함. 실제: N)r   rM   r_   r`   ra   rk   s     r   test_list_skills_returns_listz1TestLegacyFunctions.test_list_skills_returns_list2  s1    Z(&$'h+YZ^_eZfYg)hh'r   c                L    t        t              }t        |      dkD  sJ d       y)uV   실제 skills 디렉토리에서 list_skills 결과가 비어 있지 않아야 한다.r   u:   skills 디렉토리에 스킬이 1개 이상 있어야 함N)r   rM   rz   rk   s     r   test_list_skills_not_emptyz.TestLegacyFunctions.test_list_skills_not_empty7  s"    Z(6{Q\ \\r   c                v    t        t              }|D ]%  }t        |t              rJ dt	        |               y)uK   list_skills 결과의 각 항목이 SkillSummary 인스턴스여야 한다.u*   항목이 SkillSummary여야 함. 실제: N)r   rM   r_   r	   ra   ro   s      r   1test_list_skills_contains_skill_summary_instanceszETestLegacyFunctions.test_list_skills_contains_skill_summary_instances<  sB    Z( 	mDdL1l5_`dei`j_k3ll1	mr   c                j    t        t        d      }t        |t              sJ dt	        |              y)uO   view_skill('retro') 호출 시 SkillDetail 인스턴스를 반환해야 한다.r6   u@   view_skill('retro') 결과가 SkillDetail이어야 함. 실제: N)r   rM   r_   r   ra   rk   s     r   *test_view_skill_retro_returns_skill_detailz>TestLegacyFunctions.test_view_skill_retro_returns_skill_detailB  s@    J0&+.  	B2rswx~s  sA  1B  	B.r   c                    t        t        d      }t        |d      sJ t        |d      sJ t        |d      sJ t        |d      sJ t        |d      sJ y)u_   view_skill('retro') 결과에 name, description, triggers, requires 필드가 있어야 한다.r6   r+   descriptiontriggersrequires	full_pathN)r   rM   hasattr)r    details     r   *test_view_skill_detail_has_required_fieldsz>TestLegacyFunctions.test_view_skill_detail_has_required_fieldsG  s^    J0vv&&&v}---vz***vz***v{+++r   c                j    t        t        d      }t        |t              sJ dt	        |              y)u>   load_skill('pdf') 호출 시 문자열을 반환해야 한다.rU   u6   load_skill('pdf') 결과가 str이어야 함. 실제: N)r   rM   r_   rn   ra   rk   s     r   "test_load_skill_pdf_returns_stringz6TestLegacyFunctions.test_load_skill_pdf_returns_stringP  s3    J.&#&o*`aeflam`n(oo&r   c                N    t        t        d      }t        |      dkD  sJ d       y)uH   load_skill('pdf')의 반환 문자열이 비어 있지 않아야 한다.rU   r   u)   load_skill('pdf') 결과가 비어 있음N)r   rM   rz   rk   s     r   test_load_skill_pdf_not_emptyz1TestLegacyFunctions.test_load_skill_pdf_not_emptyU  s$    J.6{QK KKr   c                    t        j                  t              5  t        t        d       ddd       y# 1 sw Y   yxY w)ug   존재하지 않는 스킬 이름으로 view_skill 호출 시 FileNotFoundError가 발생해야 한다.nonexistent-skill-xyz-123N)pytestraisesFileNotFoundErrorr   rM   r0   s    r   1test_view_skill_nonexistent_raises_file_not_foundzETestLegacyFunctions.test_view_skill_nonexistent_raises_file_not_foundZ  3    ]],- 	@z#>?	@ 	@ 	@   4=c                    t        j                  t              5  t        t        d       ddd       y# 1 sw Y   yxY w)ug   존재하지 않는 스킬 이름으로 load_skill 호출 시 FileNotFoundError가 발생해야 한다.r   N)r   r   r   r   rM   r0   s    r   1test_load_skill_nonexistent_raises_file_not_foundzETestLegacyFunctions.test_load_skill_nonexistent_raises_file_not_found_  r   r   c                (    t        d      }|g k(  sJ y)u`   존재하지 않는 디렉토리로 list_skills 호출 시 빈 리스트를 반환해야 한다.z/nonexistent/path/xyzN)r   rk   s     r   .test_list_skills_nonexistent_dir_returns_emptyzBTestLegacyFunctions.test_list_skills_nonexistent_dir_returns_emptyd  s    45||r   c                H    t        t        d      }t        |t              sJ y)uU   view_skill('blog-writer') 호출 시 SkillDetail 인스턴스를 반환해야 한다.r.   N)r   rM   r_   r   rk   s     r   0test_view_skill_blog_writer_returns_skill_detailzDTestLegacyFunctions.test_view_skill_blog_writer_returns_skill_detaili  s    J6&+...r   c                h    t        t        d      }t        |t              sJ t	        |      dkD  sJ y)u@   load_skill('retro') 호출 시 문자열을 반환해야 한다.r6   r   N)r   rM   r_   rn   rz   rk   s     r   $test_load_skill_retro_returns_stringz8TestLegacyFunctions.test_load_skill_retro_returns_stringn  s.    J0&#&&&6{Qr   N)r8   r9   r:   r;   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   /  sI    Oi
]
mB
,p
L
@
@

/
r   r   )r;   
__future__r   pathlibr   r   utils.skill_loaderr   r   r   r   r	   r
   r   r   r   r   r   r   r   r   rM   fixturer   r   r=   rJ   ri   rv   r   r   r   r   <module>r      s   
 #     " *
 % %$> $>X$< $<Z8& 8&@ >7J 7J~C Cr   