
     {i#                    $   d Z ddlmZ ddlZddlZddlmZ ddlZ ed      Zedz  dz  Z	edz  dz  Z
e	d	z  Zej                  j                  d ee	             ej                  j                  d ee
             d
gg dg dg dg ddZ ej                   d      d        Zej$                  j'                  d eej+                                     d        Zh dZd Zej$                  j'                  d ee            d        Zej$                  j'                  d ee            d        Zej$                  j'                  d ee            d        Zd Zej$                  j'                  dg d      d        Zd Zd  Z d! Z!d" Z"d# Z#y)$u  IDS Phase 1 회귀 테스트 — 카드뉴스 고급화 (60 design-md × 12 satori + hybrid 5종).

검증 시나리오 (IDS plan §0.4):
1. 5 카테고리 × 1+ Stratified Sampling
2. Hybrid 패턴 H1~H5 각 1건
3. 한글 100% (구조 검증, OCR은 별도)
4. 5 사이즈 자동
5. 잘못된 design-md → fallback
6. 외부 API 직접 호출 시도 mock → 차단 확인 (IDS §0.5)

회귀 0: task-2381/2384/2387 보존 (외부 인프라 미수정).
    )annotationsN)Pathz/home/jay/workspaceskillszsatori-cardnewszhybrid-image	templatesstripe)supabasez
linear.appmongodb)airbnbspotifyintercom)ferrarilamborghinibmw)appleraycastclaude)financesaasconsumerluxurytech_minimalmodule)scopec                     ddl m}  | S )Nr   load_design_md)design_md_loaderr   r   s    C/home/jay/workspace/tests/dev6/test_ids_phase1_cardnews_advanced.pyloaderr   -   s    /    zcategory,brand_optionsc                h   |D cg c]%  }t         dz  dz  |z  dz  j                         s$|' }}|sJ d| d       |d   } | |      }dD ]  }||v rJ | d|         |d	   j                  d
      s
J | d       d|d   j                         vsJ d|d   j                         vsJ yc c}w )uU   각 카테고리당 최소 1개 브랜드의 디자인 토큰을 정상 추출한다.	resourcesz	design-mdz	DESIGN.mdz	category z has no available brandr   )	primary	secondaryaccent
backgroundtext_primaryfont_display	font_bodyspacingborder_radiusz: missing token r#   #z primary not hexz
sans-serifr(   arialN)WORKSPACE_ROOTexists
startswithlower)r   categorybrand_optionsb	availablebrandtokenskeys           r    test_design_md_load_per_categoryr9   4   s     *uqn{.J[.X[\.\_j.j-r-r-tuIuC	(+BCC9aLEE]F I >f}='7u==}> )'',H7G.HH,vn5;;====&0668888 vs
   %B/B/>   grid-4grid-6typography-focuscoverminimalcolorfulmagazine	corporate
asymmetric
monochromeinfographicstorytellingc                     t         j                  d      D  ch c]  } | j                   }} t        |z
  }|r
J d|        y c c} w )Nz*.htmlzmissing templates: )TEMPLATES_DIRglobstemEXPECTED_TEMPLATES)pfoundmissings      r   test_all_12_templates_existrN   P   sJ    *//9:QVV:E: 5(G7-gY77;w ;s   Atemplate_idc                   t         | z  }|j                  d      j                         s
J |  d       |j                  d      j                         s
J |  d       |j                  d      j                         s
J |  d       y)uF   각 템플릿은 .html + .css + .json 3-파일 구성이어야 한다..htmlz.html missingz.cssz.css missing.jsonz.json missingN)rG   with_suffixr/   )rO   bases     r   test_template_has_3_filesrU   V   s     ;&DW%--/NK=1NN/V$,,.L;-|0LL.W%--/NK=1NN/r    c                    t         |  dz  j                  d      }d|v rd|v s
J |  d       d|v s
J |  d       d	|v sd
|v s
J |  d       yy)uN   Satori 호환: <div style= 시작, 종료 태그 포함, 한글 폰트 명시.rQ   utf-8encodingz<divzstyle=z not Satori-stylez</div>z no closing tag
PretendardzNoto Sans KRz korean font missingN)rG   	read_text)rO   htmls     r   $test_template_html_satori_compatibler]   _   s     {m511<<g<NDT>h$.Q;-?P0QQ.t<}O<<D n&<c+Nb?cc=&< r    c                    t        j                  t        |  dz  j                  d            }dD ]  }||v rJ |  d|         |d   | k(  sJ y )NrR   rW   rX   )idnamedescriptionrequired_varsdefault_sizez.json missing r_   )jsonloadsrG   r[   )rO   schemar8   s      r   test_template_json_schemarg   i   sj    ZZK=)>>IISZI[\FM Bf}A^C5AA}B$<;&&&r    c                 P    ddl m}  t        | j                               h dk(  sJ y )Nr   PATTERNS>   h1h2h3h4h5)patternsrj   setkeysri   s    r   test_hybrid_patterns_5_exportrs   v   s    !x}}#AAAAr    
pattern_id)rk   rl   rm   rn   ro   c                    ddl }ddlm} |j                  ||          }t	        |j
                  j                               }dD ]  }||v rJ |  d|         y)uA   모든 패턴의 render() 시그니처가 일관되어야 한다.r   Nri   )titlebodyoutput_pathsizedesign_tokensbackground_pathprompt_hintz missing param: )inspectrp   rj   	signaturelist
parametersrr   )rt   r}   rj   sigparamsrequireds         r   test_hybrid_pattern_signaturer   |   sf     !


HZ0
1C#..%%'(Fo M6!Lj\1A(#LL!Mr    c                 "   ddl m} m}m} h d}t	        | j                               |k(  sJ  |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  |d      dk(  sJ t	         |             |k(  sJ y )Nr   )SIZESget_sizelist_platforms>   naverthreadstwitterfacebook	instagramr   )8  r   r   )  iv  r   )r   i  r   )r   iF  r   )   r   nonexistent)sizesr   r   r   rq   rr   )r   r   r   expecteds       r   test_5_sizesr      s    55GHuzz|(((K L000J;...I+---I,...G
***M"l222~ H,,,r    c                p    t        j                  t              5   | d       d d d        y # 1 sw Y   y xY w)Nznonexistent-brand-xyz-12345)pytestraisesFileNotFoundError)r   s    r   &test_invalid_brand_raises_filenotfoundr      s-    	(	) .,-. . .s   	,5c                 v    ddl m}   |        }dD ]  }||v rJ  |d   j                  d      sJ d|d   v sJ y )Nr   )fallback_tokens)r#   r$   r%   r&   r'   r(   r)   r#   r,   rZ   r(   )r   r   r0   )r   fbr8   s      r   test_fallback_tokens_safer      sS    0		Bl byyi=##C(((2n----r    c            	        d} d}t        t        j                  d            t        t        dz  j                  d            z   }|D ]-  }|j	                  d      }| D ]  }|j                         D ]l  }|j                         }|j                  d      s"|j                  d      s|j                  d	      rGd
|v sd|v rP|j                  |      sbJ | d|         |D ]  }|j                         D ]x  }d
|v s#d|v s|j                         j                  d      r+||v s0d|j                         v sd|z   |v sd|z   |v sQt        j                  | d|j                                z  0 y)u   skills/satori-cardnews 와 skills/hybrid-image/patterns 안의 모든 .py 파일에
    `import openai`, `import anthropic`, `from google.generativeai`,
    `api.openai.com` 직접 사용이 0건이어야 한다 (docstring/주석 제외).
    )zimport openaizimport anthropiczimport google.generativeaizfrom openai zfrom anthropic zfrom google.generativeai)zapi.openai.comzapi.anthropic.comz!generativelanguage.googleapis.comz*.pyrp   rW   rX   r,   z"""z'''u   ❌u   금지z: forbidden import: http"'z: forbidden URL in code: N)r   SATORI_SKILLrglobHYBRID_SKILLr[   
splitlinesstripr0   r1   r   fail)	forbidden_importsforbidden_urlstargetspathtextpatternlinestrippedurls	            r   test_no_direct_api_importsr      s   
YaN<%%f-.|j7P6W6WX^6_1``G T~~w~/( 	cG) c::<&&s+x/B/B5/IXM`M`afMgD=H$4#..w7bD6AUV^Ua9bb7c	c " 	TC) TD=H$4

8O8OPS8T$;Fdjjl$:cCi4>OSVY\S\`dSdKK4&(A$**,AQ RST	TTr    c                 b    ddl m}   |        }t        |      dk\  sJ dt        |       d       y )Nr   )list_brands2   zonly z brands; target ~60+)r   r   len)r   brandss     r   test_list_brands_meets_minimumr      s1    ,]Fv;"Gc&k]2FGGr    )$__doc__
__future__r   rd   syspathlibr   r   r.   r   r   rG   r   insertstrCATEGORY_BRANDSfixturer   markparametrizer   itemsr9   rJ   rN   sortedrU   r]   rg   rs   r   r   r   r   r   r    r    r   <module>r      s   #  
  +,(+<<(>9{* 3|$ % 3|$ % z11/2 h   148M8M8O3PQ9 R9( 8 /A(BCO DO /A(BCd Dd /A(BC' D'B 'EF	M G	M"-(.
.TFHr    