
     {i#                    >   d Z ddlmZ ddlZddlmc 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j0                  j3                  d eej7                                     d        Zh dZd Zej0                  j3                  d ee            d        Z ej0                  j3                  d ee            d        Z!ej0                  j3                  d ee            d        Z"d Z#ej0                  j3                  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                   |D cg c]%  }t         dz  dz  |z  dz  j                         s$|' }}|st        j                  d| d      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            |d	   } | |      }d
D ]  }||v }	|	st        j                  d|	fd||f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |      rt        j                  |      nddz  }
t        j                  | d|       dz   d|
iz  }t        t        j                  |            d}	 |d   }|j                  }d} ||      }|st        j                  | d      dz   t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}}d}|d   }|j                  } |       }||v}|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}d }|d   }|j                  } |       }||v}|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}yc c}w )!uU   각 카테고리당 최소 1개 브랜드의 디자인 토큰을 정상 추출한다.	resourcesz	design-mdz	DESIGN.mdz	category z has no available brandz
>assert %(py0)spy0	availabler   )	primary	secondaryaccent
backgroundtext_primaryfont_display	font_bodyspacingborder_radiusinz%(py0)s in %(py2)skeytokensr#   py2z: missing token 
>assert %(py4)spy4Nr%   #z primary not hexzN
>assert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
}py1py3py5py7z
sans-serifr*   )not in)zH%(py1)s not in %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.lower
}()
})r9   r6   py6py8zassert %(py10)spy10arial)WORKSPACE_ROOTexists
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation_call_reprcompare
startswithlower)r   categorybrand_optionsbr$   @py_format1brandr2   r1   @py_assert1@py_format3@py_format5@py_assert0@py_assert2@py_assert4@py_assert6@py_format8@py_assert3@py_assert5@py_assert7@py_format9@py_format11s                         r    test_design_md_load_per_categoryra   4   s    *uqn{.J[.X[\.\_j.j-r-r-tuIuCC	(+BCCCCCCC9CCC9CCCCCaLEE]F I >f}===sf======s===s======f===f===='7u=======> )H''HH',H,HH7G.HHHHHHH'HHHHHH,HHHHHH=vn5=5;;=;==<=====<====<===5===;===========8&08066868878888878888788808886888888888888 vs
   %OO>   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
  }| }|s~t	        j
                  d|       dz   ddt        j                         v st	        j                  |      rt	        j                  |      ndiz  }t        t	        j                  |            d }y c c} w )Nz*.htmlzmissing templates: z
>assert not %(py0)sr#   missing)TEMPLATES_DIRglobstemEXPECTED_TEMPLATESrD   rE   rF   rG   rH   rI   rJ   rK   )pfoundro   rT   @py_format2s        r   test_all_12_templates_existrw   P   s~    *//9:QVV:E: 5(G;7;77-gY7777777w777w777777 ;s   B<template_idc           	        t         | z  }|j                  }d} ||      }|j                  } |       }|st        j                  |  d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}x}}|j                  }d} ||      }|j                  } |       }|st        j                  |  d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}x}}|j                  }d	} ||      }|j                  } |       }|st        j                  |  d
      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}x}}y)uF   각 템플릿은 .html + .css + .json 3-파일 구성이어야 한다..htmlz.html missingz
>assert %(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.with_suffix
}(%(py4)s)
}.exists
}()
}base)r#   r4   r6   r>   r?   r@   Nz.cssz.css missing.jsonz.json missing)rp   with_suffixrC   rD   rE   rF   rG   rH   rI   rJ   rK   )rx   r{   rT   r\   r]   r^   @py_assert9r`   s           r   test_template_has_3_filesr   V   s$    ;&DNWNW%N%--N-/N/NNK=1NNNNNNNDNNNDNNNNNNWNNN%NNN-NNN/NNNNNNNLVLV$L$,,L,.L.LL;-|0LLLLLLLDLLLDLLLLLLVLLL$LLL,LLL.LLLLLLLNWNW%N%--N-/N/NNK=1NNNNNNNDNNNDNNNNNNWNNN%NNN-NNN/NNNNNNNr    c                   t         |  dz  j                  d      }g }d}||v }|}|rd}||v }|}|sqt        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }d
d|iz  }	|j                  |	       |rt        j                  dfd|f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }
dd|
iz  }|j                  |       t        j                  |d      i z  }t        j                  |  d      dz   d|iz  }t        t        j                  |            dx}x}x}x}x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  |  d      dz   d|iz  }t        t        j                  |            dx}}g }d}||v }|}|sd}||v }|}|sqt        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }d
d|iz  }	|j                  |	       |st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }
dd|
iz  }|j                  |       t        j                  |d      i z  }t        j                  |  d      dz   d|iz  }t        t        j                  |            dx}x}x}x}x}}y)uN   Satori 호환: <div style= 시작, 종료 태그 포함, 한글 폰트 명시.rz   utf-8encodingz<divzstyle=r.   )z%(py3)s in %(py5)shtml)r:   r;   z%(py7)sr<   )z%(py10)s in %(py12)s)r@   py12z%(py14)spy14r   z not Satori-stylez
>assert %(py17)spy17Nz</div>)z%(py1)s in %(py3)sr9   r:   z no closing tagz
>assert %(py5)sr;   
PretendardzNoto Sans KR   z korean font missing)rp   	read_textrD   rL   rI   rF   rG   rH   append_format_booloprE   rJ   rK   )rx   r   rT   rX   rY   rW   r~   @py_assert11@py_format6r[   @py_format13@py_format15@py_format16@py_format18@py_format4s                  r   $test_template_html_satori_compatibler   _   s=    {m511<<g<NDQ6Q6T>QhQh$.QQQQ6TQQQ6QQQQQQTQQQTQQQQQQQh$QQQhQQQQQQ$QQQ$QQQQQQQQQQ;-?P0QQQQQQQQ<8t<<<8t<<<8<<<<<<t<<<t<<<<}O<<<<<<<cLcLD cncn&<ccccLDcccLccccccDcccDcccccccncccnccccccccccccccccccc+Nb?ccccccccr    c                   t        j                  t        |  dz  j                  d            }dD ]  }||v }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      ndd	z  }t	        j                  |  d
|       dz   d|iz  }t        t	        j                  |            d } |d   }|| k(  }|st	        j
                  d|fd|| f      t	        j                  |      dt        j                         v st	        j                  |       rt	        j                  |       nddz  }dd|iz  }	t        t	        j                  |	            d x}}y )Nr|   r   r   )idnamedescriptionrequired_varsdefault_sizer.   r0   r1   schemar3   z.json missing r5   r6   r   ==)z%(py1)s == %(py3)srx   r   zassert %(py5)sr;   )jsonloadsrp   r   rD   rL   rF   rG   rH   rI   rE   rJ   rK   )
rx   r   r1   rT   rU   rV   rW   rX   r   r   s
             r   test_template_json_schemar   i   s   ZZK=)>>IISZI[\FM Bf}AAAsfAAAAAAsAAAsAAAAAAfAAAfAAAA^C5AAAAAAAB$<&<;&&&&<;&&&<&&&&&&;&&&;&&&&&&&r    c            	        ddl m}  | j                  } |       }t        |      }h d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |       rt	        j                  |       ndt	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            d x}x}x}x}}y )Nr   PATTERNS>   h1h2h3h4h5r   )zb%(py7)s
{%(py7)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.keys
}()
})
} == %(py10)ssetr   )r#   r9   r:   r;   r<   r@   zassert %(py12)sr   )patternsr   keysr   rD   rL   rF   rG   rH   rI   rJ   rK   )r   rX   rY   rZ   r~   @py_assert8r`   r   s           r   test_hybrid_patterns_5_exportr   v   s    !}}A}A3A#AA#AAAAA#AAAAAAA3AAA3AAAAAAxAAAxAAA}AAAAAAAAA#AAAAAAAAAr    
pattern_id)r   r   r   r   r   c                f   ddl }ddlm} |j                  ||          }t	        |j
                  j                               }dD ]  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      ndd	z  }t        j                  |  d
|       dz   d|iz  }t        t        j                  |            d} y)uA   모든 패턴의 render() 시그니처가 일관되어야 한다.r   Nr   )titlebodyoutput_pathsizedesign_tokensbackground_pathprompt_hintr.   r0   requiredparamsr3   z missing param: r5   r6   )inspectr   r   	signaturelist
parametersr   rD   rL   rF   rG   rH   rI   rE   rJ   rK   )	r   r   r   sigr   r   rT   rU   rV   s	            r   test_hybrid_pattern_signaturer   |   s     !


HZ0
1C#..%%'(Fo M6!LLLx6LLLLLLxLLLxLLLLLL6LLL6LLLLj\1A(#LLLLLLLMr    c            	        ddl m} m}m} h d}| j                  } |       }t        |      }||k(  }|sKt        j                  d|fd||f      dt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }d
d|iz  }	t        t        j                  |	            d x}x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}}d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}} |       }t        |      }||k(  }|s7t        j                  d|fd||f      dt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   )SIZESget_sizelist_platforms>   naverthreadstwitterfacebook	instagramr   )za%(py7)s
{%(py7)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.keys
}()
})
} == %(py9)sr   r   expected)r#   r9   r:   r;   r<   py9zassert %(py11)spy11r   )8  r   )z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr   )r#   r4   r6   r<   zassert %(py9)sr   r   )  iv  r   )r   i  r   )r   iF  r   )   r   nonexistent)zG%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s()
})
} == %(py7)sr   )r#   r9   r:   r;   r<   )sizesr   r   r   r   r   rD   rL   rF   rG   rH   rI   rJ   rK   )r   r   r   r   rX   rY   rZ   r   @py_format10@py_format12rT   r\   r]   r[   s                 r   test_5_sizesr      s   55GHzz(z|(3|(((((((((((3(((3((((((u(((u(((z(((|(((((((((((((((((((08K 0L0 L0000 L00000080008000K000 000L0000000.8J.;.;....;......8...8...J......;.......-8I-+-+----+------8---8---I------+-------.8I.,.,....,......8...8...I......,.......*8G*
*
****
******8***8***G******
*******!28M"2l2"l2222"l22222282228222M222"222l2222222,3 , H,,,, H,,,,,,3,,,3,,,,,,~,,,~,,,,,, ,,,,,,H,,,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                    ddl m}   |        }dD ]  }||v }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |      rt        j                  |      nddz  }d	d
|iz  }t        t        j                  |            d } |d   }|j                  }d} ||      }	|	stdt        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }
t        t        j                  |
            d x}x}x}}	d}|d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   )fallback_tokens)r%   r&   r'   r(   r)   r*   r+   r.   r0   r1   fbr3   zassert %(py4)sr6   r%   r7   zLassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
}r8   r   r*   )z%(py1)s in %(py4)s)r9   r6   zassert %(py6)sr>   )r   r   rD   rL   rF   rG   rH   rI   rJ   rK   rM   )r   r   r1   rT   rU   rV   rW   rX   rY   rZ   r[   r\   @py_format7s                r   test_fallback_tokens_safer      s:   0		Bl bysbssbbi=(=##(C(#C(((((=(((#(((C((((((((((-2n--<-----<----<-----------r    c            
        d} d}t        t        j                  d            t        t        dz  j                  d            z   }|D ]  }|j	                  d      }| D ]o  }|j                         D ]X  }|j                         }|j                  d      s"|j                  d      s|j                  d	      rHd
|v sd|v rQ|j                  } ||      }	|	 }
|
st        j                  | d|      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      dz  }t        t        j                  |            dx}x}	}
[ r |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   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*.pyr   r   r   r7   z"""z'''u   ❌u   금지z: forbidden import: zR
>assert not %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py3)s)
}strippedpattern)r#   r4   r:   r;   Nhttp"'z: forbidden URL in code: )r   SATORI_SKILLrglobHYBRID_SKILLr   
splitlinesstriprM   rD   rE   rF   rG   rH   rI   rJ   rK   rN   r   fail)forbidden_importsforbidden_urlstargetspathtextr   liner   rT   rY   rZ   r   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#..b.w7b77b7bbD6AUV^Ua9bbbbbbb8bbb8bbb.bbbbbbwbbbwbbb7bbbbbbc	c " 	TC) TD=H$4

8O8OPS8T$;Fdjjl$:cCi4>OSVY\S\`dSdKK4&(A$**,AQ RST	TTr    c                    ddl m}   |        }t        |      }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d	t        |       d
      dz   d|iz  }t        t        j                  |            d x}x}}y )Nr   )list_brands2   )>=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)slenbrands)r#   r9   r:   r>   zonly z brands; target ~60+z
>assert %(py8)sr?   )r   r   r   rD   rL   rF   rG   rH   rI   rE   rJ   rK   )r   r   rX   r]   rY   r   r_   s          r   test_list_brands_meets_minimumr      s    ,]Fv;G"G;"GGG;"GGGGGG3GGG3GGGGGGvGGGvGGG;GGG"GGGc&k]2FGGGGGGGGr    )*__doc__
__future__r   builtinsrF   _pytest.assertion.rewrite	assertionrewriterD   r   syspathlibr   r   rB   r   r   rp   r   insertstrCATEGORY_BRANDSfixturer   markparametrizer   itemsra   rs   rw   sortedr   r   r   r   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    