
    KiM                    p   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ZddlZddl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mZ ddlZddlmZ dZd	Zd!d
Zd"d#dZ  e        ddgZ! ed      jE                         xs  ejF                  d      duZ$ejJ                  jM                  e$ d      Z'ejP                  d$d       Z)ejP                  d%d       Z* ejP                  d      d&d       Z+d'dZ,d'dZ-d'dZ.e'd(d       Z/e'd)d       Z0e'd)d       Z1d*dZ2d+dZ3d*dZ4d*dZ5d*dZ6d'd Z7y),u=  IDS Phase 5 — 모션 카드뉴스 (HTML→MP4) 테스트.

테스트 커버리지:
1. SNS 규격 상수 검증 (3종)
2. 5가지 모션 효과 상수 검증
3. 알 수 없는 효과 ValueError 검증
4. 실제 ffmpeg로 MP4 렌더링 (L1 스모크)
5. 키프레임 추출 (첫/중/끝 3개)
6. OCR 검증 — pytesseract 없이 폴백
7. 큐 enqueue + process 성공
8. 큐 동시성 제한 검증
9. 큐 재시도 동작 검증
10. 큐 타임아웃 → TIMEOUT 상태
11. 외부 네트워크 호출 금지 검증 (§0.5)
12. BGM 라이선스 검증 — 거부 케이스
    )annotationsN)ThreadPoolExecutor)Path)patch)Imagez-/home/jay/workspace/skills/motion-cardnews-komotion_cardnews_koc                 
   t         } t        t              }dD ]Q  }|  d| }|t        j                  vst
        j                  j                  ||| dz        }g }d}||u}|}|r|j                  }	d}
|	|
u}|}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d	d
|iz  }|j                  |       |rt        j                  dfd	
f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }t        j"                  d| d|       dz   d|iz  }t%        t        j&                  |            dx}x}x}x}x}	x}}
t
        j                  j)                  |      }| |_        |t        j                  |<   	 |j                  j-                  |       T | t        j                  vr;t
        j                  j                  | |dz  t1        |      g      }g }d}||u}|}|r|j                  }	d}
|	|
u}|}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d	d
|iz  }|j                  |       |rt        j                  dfd	
f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }t        j"                  d|  d      dz   d|iz  }t%        t        j&                  |            dx}x}x}x}x}	x}}
t
        j                  j)                  |      }| |_        |t        j                  | <   	 |j                  j-                  |       t        j                  |    S # t.        $ r t        j                  |=  w xY w# t.        $ r t        j                  | =  w xY w)u8   스킬 패키지 전체를 로드하고 반환합니다.)sizeseffectsframesrenderocrbgm.z.pyNis notz%(py2)s is not %(py5)sspecpy2py5%(py7)spy7z5%(py11)s
{%(py11)s = %(py9)s.loader
} is not %(py14)spy9py11py14%(py16)spy16r   failed to load  from 
>assert %(py19)spy19z__init__.py)submodule_search_locations	init_specz from __init__.py)_SKILL_MODULE_NAMEr   
_SKILL_DIRsysmodules	importlibutilspec_from_file_locationloader
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprappend_format_boolop_format_assertmsgAssertionError_format_explanationmodule_from_spec__package__exec_module	Exceptionstr)pkg_name	skill_dirsubmodmod_namer   @py_assert1@py_assert4@py_assert3@py_assert0@py_assert10@py_assert13@py_assert12@py_format6@py_format8@py_format15@py_format17@py_format18@py_format20modr&   pkg_mods                        H/home/jay/workspace/tests/design-team/test_ids_phase5_motion_cardnews.py_load_skill_packagerS   '   si   !H Z IH Zq)3;;&>>99vhcN*D mtl4t#ll4l4(?llll4tllllll4lll4llltlllllll4lllllllllllllll4llllllllll?SYRZZ`aj`kAlllllllll..11$7C&CO$'CKK!''," s{{"NN::%(+I'7 ; 
	
 	uty$t)9)9tt)9)Ettttyttttttytttytttttttttt)9tttttttttttt)9tttttttttttttYaXbbsGttttttttt..11)<& 'H	((1
 ;;x  )  KK)   	H%	s   S S- S*-T	c                   t        d      }| t        j                  v rt        j                  |    S t        j                  j                  | t        |            }g }d}||u}|}|r|j                  }d}||u}	|	}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }
dd|
iz  }|j                  |       |rt        j                  d	fd	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d
z  }dd|iz  }|j                  |       t        j                  |d      i z  }t        j                   d|  d|       dz   d|iz  }t#        t        j$                  |            dx}x}x}x}x}x}	}t        j                  j'                  |      }|t        j                  | <   	 |j                  j)                  |       |S # t*        $ r t        j                  | =  w xY w)u8   렌더 큐 스크립트를 동적으로 로드합니다.z2/home/jay/workspace/scripts/motion_render_queue.pyNr   r   r   r   r   r   r   r   r   r    r   r!   r"   r#   r$   )r   r)   r*   r+   r,   r-   r>   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   r<   r=   )module_alias
queue_pathr   rC   rD   rE   rF   rG   rH   rI   rJ   rK   rL   rM   rN   rO   rP   s                    rR   _load_queue_modulerW   Q   s   JKJs{{"{{<((>>11,JPDktk4tkk4k4 7kkkk4tkkkkkk4kkk4kkktkkkkkkk4kkkkkkkkkkkkkkk4kkkkkkkkkk?<.X^_i^j9kkkkkkkkk
..
)
)$
/C #CKK$ J  KK%s   I# #I?/home/jay/.local/bin/ffmpegffmpegzffmpeg required but not found)reasonc                      y)u.   테스트용 소형 해상도 (속도 향상).   r]    r^       rR   
small_sizer`   r   s     r_   c                    t         j                  t         d   j                  }|\  }}g d}g d}g }t	        t        ||            D ].  \  }\  }	}
| d| dz  } ||||	|
|       |j                  |       0 |S )uN   3개의 단색 PIL 프레임을 생성하고 경로 목록을 반환합니다..frames))   2   rd   )rd      rd   )rd   rd   rc   )u	   첫번째u	   두번째u	   세번째frame_.png)r)   r*   r'   generate_solid_frame	enumeratezipr5   )tmp_pathr`   rh   whcolorstextsr   icolortextouts               rR   three_framesrt   x   s     ;;*<)=W'EF[[DAq:F3EF%c&%&89 =E46!D))Q5$4c Mr_   session)scopec                   t         st        j                  d       | j                  d      }t        j
                  t         d   j                  }t        j
                  t         d   j                  }d}g d}g d}g }t        t        ||            D ]4  \  }\  }	}
|d| d	z  } ||d
   |d   |	|
|       |j                  |       6 |dz  } ||||ddd       |S )u2   세션 공유 MP4 파일 (test 4용). 5초 분량.zffmpeg not availablesession_mp4rb   z.renderr\   ))re   d   rd   )rd      re   )ry   re   P   u   시작u   중간u   끝rf   rg   r      ztest_output.mp4fade
   gQ?)frame_pathsoutput_pathsizeeffectfpsduration_per_frame)_FFMPEG_AVAILABLEpytestskipmktempr)   r*   r'   rh   render_motionri   rj   r5   )tmp_path_factoryrk   rh   r   r   rn   ro   r   rp   rq   rr   rs   outputs                rR   rx   rx      s     *+&&}5H;;*<)=W'EF[[KK#5"6g >?MMMD=F'E K%c&%&89  =E46!D))T!Wd1gudC@3 
 ))F Mr_   c                    t         j                  t         d   } | j                  }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}}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}}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}}|d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d
x}x}}|d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d
x}x}}|d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d
x}x}}y
)uT   instagram_reels(1080×1920), twitter(1920×1080), threads(1080×1080) 정의 확인.z.sizesinstagram_reelsin)z%(py1)s in %(py3)sSIZES)py1py3u)   instagram_reels가 SIZES에 없습니다.z
>assert %(py5)sr   Ntwitteru!   twitter가 SIZES에 없습니다.threadsu!   threads가 SIZES에 없습니다.)8    ==z%(py1)s == %(py4)sr   py4assert %(py6)spy6)r   r   )r   r   )r)   r*   r'   r   r/   r0   r4   r1   r2   r3   r7   r8   r9   )		sizes_modr   rF   @py_assert2@py_format4rJ   rE   @py_format5@py_format7s	            rR   test_3_sns_sizes_definedr      sK   12&9:IOOER%RRRRRRRRRRRRRRRRRRR'RRRRRRRB9BBB9BBB9BBBBBBBBBBBBBBBBBBBBB9BBB9BBB9BBBBBBBBBBBBBBBBBBBB"#3|3#|3333#|333#333|3333333+|+|++++|++++++|++++++++|+|++++|++++++|+++++++r_   c                 .   t         j                  t         d   } | 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	      d
z   d|iz  }t        t	        j                  |            d} y)uO   fade, slide, zoom, dissolve, sequence 키가 EFFECTS에 존재하는지 확인..effects)r~   slidezoomdissolvesequencer   )z%(py0)s in %(py2)sr   EFFECTS)py0r   u   효과 'u   '이 EFFECTS에 없습니다.z
>assert %(py4)sr   N)r)   r*   r'   r   r/   r0   r1   r2   r3   r4   r7   r8   r9   )effects_modr   r   rC   @py_format3r   s         rR   test_5_motion_effects_definedr      s    ++!3 4H=>K!!GC S RRRvRRRRRRvRRRvRRRRRRRRRRRRRHVH4Q"RRRRRRRSr_   c                     t         j                  t         d   } | j                  }t	        j
                  t        d      5   |ddd       ddd       y# 1 sw Y   yxY w)	uL   알 수 없는 효과 이름에 대해 ValueError가 발생하는지 확인.r   u   알 수 없는 효과matchunknown_effect_xyz   g      ?)r   durationN)r)   r*   r'   get_effect_filterr   raises
ValueError)r   r   s     rR   %test_get_effect_filter_unknown_raisesr      sZ    ++!3 4H=>K#55	z)@	A F.BEF F Fs   AA c                \   | j                   } |       }|st        j                  d|        dz   dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}| j                  } |       }|j                  }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      d	z  }t        j                  d
| j                         j                   d      dz   d|iz  }t        t        j                  |            dx}x}x}x}}t        t        d      j                  dz        }	t        |	      j                         st        j                   d      xs d}	|	r]t#        j$                  |	ddddddt        |       gdd      }
|
j&                  dk(  r#|
j(                  j+                         rt-        |
j(                  j+                               }d}||k  }d}||k  }|r|st        j                  d||fd|||f      t        j                  |      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d | d!      d"z   d#|iz  }t        t        j                  |            dx}x}x}}yyyy)$u_   실제 ffmpeg로 5초 MP4를 렌더링하고 파일 크기와 지속 시간을 검증합니다.u)   MP4 파일이 존재하지 않습니다: C
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}rx   r   r   r   Ni   >=)z`%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.stat
}()
}.st_size
} >= %(py9)sr   r   r   r   r   u#   MP4 파일이 너무 작습니다: z bytes
>assert %(py11)sr   rX   ffprobe z-verrorz-show_entrieszformat=durationz-ofz"default=noprint_wrappers=1:nokey=1T)capture_outputrr   r   g      @g       @)<=r   )z%(py1)s <= %(py4)sz%(py4)s <= %(py6)sr   )r   r   r   u:   MP4 지속 시간이 예상 범위를 벗어났습니다: u   초z
>assert %(py8)spy8)existsr/   r7   r1   r2   r3   r4   r8   r9   statst_sizer0   r>   r   parentshutilwhich
subprocessrun
returncodestdoutstripfloat)rx   rC   rE   r   @py_assert5@py_assert8@py_assert7@py_format10@py_format12ffprobe_binresultr   rF   r   r   @py_format9s                   rR   !test_render_short_mp4_real_ffmpegr      s    ZZZZ#L[M!ZZZZZZZ;ZZZ;ZZZZZZZZZZZZww%%ww%-www%wwwwww;www;wwwwwwwww%wwwwww1TU`UeUeUgUoUoTppv/wwwwwwww d89@@9LMK##%ll9-3T7!2;K 	  	
 !fmm&9&9&;V]]0023Hu3()ucu(c)uuuu3(cuuu3uuuuuu(uuu(uuucuuu-ghpgqqt+uuuuuuuu '<! r_   c           	        t         j                  t         d   }|j                  }|dz  } || |      \  }}}d|fd|fd|ffD ]  \  }}	|	j                  }
 |
       }|st        j                  | d|	       dz   dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      d	z  }t        t        j                  |            d
x}
}|	j                  }
 |
       }|j                  }d}||kD  }|st        j                  d|fd||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }t        j                  | d      dz   d|iz  }t        t        j                  |            d
x}
x}x}x}}|	j                   }
d}|
|k(  }|st        j                  d|fd|
|f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}
x}} y
)uE   MP4에서 첫/중간/끝 3개의 키프레임 PNG를 추출합니다..ocr	keyframesfirstmiddlelastu&    키프레임 파일이 없습니다: r   
frame_pathr   Nr   >)z_%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.stat
}()
}.st_size
} > %(py9)sr   u#    키프레임이 비어있습니다r   r   rg   r   )z.%(py2)s
{%(py2)s = %(py0)s.suffix
} == %(py5)s)r   r   r   zassert %(py7)sr   )r)   r*   r'   extract_keyframesr   r/   r7   r1   r2   r3   r4   r8   r9   r   r   r0   suffix)rx   rk   ocr_modr   
output_dirr   r   r   labelr   rC   rE   r   r   r   r   r   r   rD   rJ   rK   s                        rR   &test_extract_keyframes_returns_3_pathsr      s    kk/056G11K'J+KDE64&.60BVTNS +z  ` "`"``ug-ST^S_$```````z```z``` ```"``````[ [ (([1[(1,[[[(1[[[[[[z[[[z[[[[[[ [[[([[[1[[[7Z.[[[[[[[[  *F* F**** F******z***z*** ***F*******+r_   c           
     h   t         j                  t         d   }|j                  }|dz  } || g d|      }t	        |t
              }|s!t        j                  d      dz   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d	t        j                         v st        j                  t
              rt        j                  t
              nd	t        j                  |      d
z  }t        t        j                  |            d}|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}}dD ]l  }||   }t	        |t
              }|sd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d	t        j                         v st        j                  t
              rt        j                  t
              nd	t        j                  |      d
z  }t        t        j                  |            d}|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}}|d   }t	        |t"              }	|	sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t"              rt        j                  t"              ndt        j                  |	      dz  }t        t        j                  |            dx}}	|d   }t	        |t"              }	|	sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t"              rt        j                  t"              ndt        j                  |	      dz  }t        t        j                  |            dx}}	o y)uV   pytesseract 없이 validate_korean_frames가 3개 엔트리를 반환하는지 확인.r   ocr_outr|   )expected_korean_charsr   u   결과가 dict여야 합니다z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancer   dict)r   r   r   r   N>   r   r   r   r   )zb%(py7)s
{%(py7)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.keys
}()
})
} == %(py10)sset)r   r   r   r   r   py10zassert %(py12)spy12)r   r   r   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}entry>   framefallbackocr_texthas_expectedr   )zb%(py7)s
{%(py7)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.keys
}()
})
} >= %(py10)sr   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}bool)r   r   r   r   r   )r)   r*   r'   validate_korean_framesr   r   r/   r7   r1   r2   r3   r4   r8   r9   keysr   r0   r   )rx   rk   r   r   r   r   rE   r   r   rD   @py_assert6@py_assert9r   @py_format11@py_format13r   r   rC   rJ   s                      rR   (test_ocr_validate_korean_frames_fallbackr      s    kk/056G$;;I%J#9F fd#E#EE%EEEEEEE:EEE:EEEEEEfEEEfEEEEEEdEEEdEEE#EEEEEE{{<{}<3}<!<<!<<<<<!<<<<<<<3<<<3<<<<<<v<<<v<<<{<<<}<<<<<<!<<<<<<<<, 3u%&&&&&&&&z&&&z&&&&&&%&&&%&&&&&&&&&&&&&&&&&&&::U:<Us< U$UU $UUUUU $UUUUUUUsUUUsUUUUUU5UUU5UUU:UUU<UUU UUU$UUUUUUUU/6z/66666666z666z666/6666666666666666666
+2z+T22222222z222z222+222222T222T22222222223r_   c           
        t        d      }t        | dz        }|t        j                  d<   	 |j	                  |D cg c]  }t        |       c}t        | dz        ddgdddd	d
      }|j	                  |D cg c]  }t        |       c}t        | dz        ddgdddd	d
      }|j                  ddd      }|d   }d}	||	k(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}
}	|d   }d}	||	k(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}
}	|d   }d}	||	k(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}
}	t        j                  j                  dd	       y	c c}w c c}w # t        j                  j                  dd	       w xY w)uS   2개 작업을 큐에 추가하고 처리 후 모두 SUCCESS인지 확인합니다.motion_render_queue_t7
queue_testMOTION_QUEUE_DIRzjob1_output.mp4r]   r~   r         ?Nr   r   r   r   r   r   bgm_pathzjob2_output.mp4r      <   r}   max_concurrenttimeout_per_jobmax_retriestotalr   r   r   r   r   successfailedr   )rW   r>   osenvironenqueueprocess_queuer/   r0   r4   r8   r9   pop)rk   rt   mq	queue_dirpjob1_idjob2_idsummaryrF   rE   r   r   r   s                rR   &test_queue_enqueue_and_process_successr    s   	4	5BH|+,I%.BJJ!"1**,89qCF9x*;;<#J"%
  **,89qCF9x*;;<#J "%
  ""!RUV"Ww$1$1$$$$1$$$$$$1$$$$$$$y!&Q&!Q&&&&!Q&&&!&&&Q&&&&&&&x %A% A%%%% A%%% %%%A%%%%%%%


)401 : : 	

)40s)   I) I,I) ;I$F1I) 
I) )"Jc           
     4  
 t        d      }t        | dz        }|t        j                  d<   	  G 
fdd      
g 
_        dd}|j                  |D cg c]  }t        |       c}t        | dz        ddgd	d
ddd       t        j                  |d
      5  t        j                  |d|      5  |j                  ddd       ddd       ddd       d 
j                  D        }t        |      }|st        j                  d
j                         dz   dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }	t!        t        j"                  |	            dx}}t        j                  j%                  dd       yc c}w # 1 sw Y   xY w# 1 sw Y   xY w# t        j                  j%                  dd       w xY w)uM   ThreadPoolExecutor가 max_concurrent=3으로 호출되는지 확인합니다.motion_render_queue_t8queue_concurrencyr  c                  D    e Zd ZU g Zded<   dd	 fdZd
dZddZddZy)1test_queue_concurrency_limit.<locals>.SpyExecutorz	list[int]_callsc                ^    j                   j                  |       t        |      | _        y )N)max_workers)r!  r5   r   _real)selfr#  kwargsSpyExecutors      rR   __init__z:test_queue_concurrency_limit.<locals>.SpyExecutor.__init__:  s#    ""))+6/KH
r_   c                :    | j                   j                          | S N)r$  	__enter__)r%  s    rR   r+  z;test_queue_concurrency_limit.<locals>.SpyExecutor.__enter__>  s    

$$&r_   c                6     | j                   j                  |  y r*  )r$  __exit__)r%  argss     rR   r-  z:test_queue_concurrency_limit.<locals>.SpyExecutor.__exit__B  s    #

##T*r_   c                B     | j                   j                  |g|i |S r*  )r$  submit)r%  fnr.  r&  s       rR   r0  z8test_queue_concurrency_limit.<locals>.SpyExecutor.submitE  s#    (tzz((=d=f==r_   N)r}   )r#  intr&  objectreturnNone)r4  z'SpyExecutor')r.  r3  r4  r5  )r1  r3  r.  r3  r&  r3  r4  r3  )	__name__
__module____qualname__r!  __annotations__r(  r+  r-  r0  )r'  s   rR   r'  r   7  s"     "FI"I+>r_   r'  c                >    d| d<   t        j                          | d<   | S )NSUCCESSstatuscompleted_at)timejob_datas    rR   fast_executez2test_queue_concurrency_limit.<locals>.fast_executeK  s!    !*HX'+yy{H^$Or_   zconcurrency_dummy.mp4r]   r~   r   r  Nr  r   _execute_jobside_effect   r   r   r	  c              3  &   K   | ]	  }|d k(    yw)rE  Nr^   ).0rl   s     rR   	<genexpr>z/test_queue_concurrency_limit.<locals>.<genexpr>_  s     6a166s   u:   max_workers=3으로 호출되지 않았습니다. 실제: z.
>assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr   r@  r   r4  r   )rW   r>   r  r  r!  r  r   r3  r  rI  r/   r7   r1   r2   r3   r4   r8   r9   r  )rk   rt   monkeypatchr  r  rA  r  rC   rE   r   r'  s             @rR   test_queue_concurrency_limitrL  /  s   	4	5BH223I%.BJJ!"-1	> 	>"  	
 	

,89qCF9x*AAB#J"%
 	 \\"2K@ 	Vb.lK V  2ST UV	V
 7;#5#56  	Js66  	J6  	J  	J:t  vA  vH  vH  uI  9J  	J  	J  	J  	J  	J  	Js  	J  	J  	Js  	J  	J  	J6  	J  	J  	J6  	J  	J  	J  	J  	J  	J 	

)40# :V V	V 	V 	

)40sN   &G5 G'4G5 G(4G	G(C$G5 G5 G%	 G((G2-G5 5"Hc           
     j   t        d      }t        | dz        }|t        j                  d<   	 |j	                  |D cg c]  }t        |       c}t        | dz        ddgdddd	d
       ddidfd}t        j                  |d|      5  |j                  ddd      }d	d	d	       j                  }d}d}	 |||	      }
d}|
|kD  }|st        j                  d|fd|
|f      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        j                  d|       dz   d|iz  }t!        t        j"                  |            d	x}x}x}	x}
x}}t        j                  j%                  dd	       y	c c}w # 1 sw Y   axY w# t        j                  j%                  dd	       w xY w)u]   render_motion이 실패하면 재시도하고 retry_total이 증가하는지 확인합니다.motion_render_queue_t9queue_retryr  zretry_output.mp4r]   r~   r   r  Nr  nr   c                    dxx   dz  cc<   d   dk  rt        dd    d      d| d<   t        j                         | d<   | S )NrP  r}   u   의도적 실패 (시도 )r;  r<  r=  )RuntimeErrorr>  )r@  attempt_counts    rR   patched_execute_jobz8test_queue_retry_on_failure.<locals>.patched_execute_jobx  s[    #!#S!Q&"%>}S?Q>RRS#TUU!*HX'+yy{H^$Or_   rB  rC  r}   r  r  r	  retry_totalr   zR%(py8)s
{%(py8)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s, %(py6)s)
} > %(py11)sr  r   r   r   r   r   r   u+   재시도가 기록되지 않았습니다: 
>assert %(py13)spy13rJ  rW   r>   r  r  r  r   r3  r  getr/   r0   r1   r2   r3   r4   r7   r8   r9   r  )rk   rt   r  r  r  rU  r  rC   rE   r   r   rG   r   r   @py_format14rT  s                  @rR   test_queue_retry_on_failurer^  e  s   	4	5BH},-I%.BJJ!"1


,89qCF9x*<<=#J"%
 	 a	 \\"n:MN 	\&&aYZ&[G	\ {{i=i!i{=!,iqi,q0iii,qiiiiiiwiiiwiii{iii=iii!iii,iiiqiii4_`g_h2iiiiiiii 	

)403 :&	\ 	\ 	

)40s5   H G>?H H$D9H >H HH "H2c                   t        d      }t        | dz        }|t        j                  d<   	 |j	                  |D cg c]  }t        |       c}t        | dz        ddgdddd	d
       dd}t        j                  |d|      5  |j                  ddd      }d	d	d	       j                  }d}d}	 |||	      }
|j                  }d}d} |||      }|
|z   }d}||kD  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      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        j                  d|       dz   d|iz  }t!        t        j"                  |            d	x}x}x}	x}
x}x}x}x}x}x}}t        j                  j%                  dd	       y	c c}w # 1 sw Y   xY w# t        j                  j%                  dd	       w xY w)uW   render가 타임아웃을 초과하면 TIMEOUT 또는 FAILED 상태가 기록됩니다.motion_render_queue_t10queue_timeoutr  ztimeout_output.mp4r]   r~   r   r  Nr  c                0    t        j                  d       | S )Nr   )r>  sleepr?  s    rR   slow_executez5test_queue_timeout_marks_failed.<locals>.slow_execute  s    JJrNOr_   rB  rC  r}   r  r   r	  timeoutr  r   )z(%(py8)s
{%(py8)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s, %(py6)s)
} + %(py17)s
{%(py17)s = %(py11)s
{%(py11)s = %(py9)s.get
}(%(py13)s, %(py15)s)
}) > %(py21)sr  )r   r   r   r   r   r   r   rZ  py15py17py21u5   타임아웃/실패가 기록되지 않았습니다: z
>assert %(py23)spy23rJ  r[  )rk   rt   r  r  r  rd  r  rC   rE   r   r   rG   rI   @py_assert14@py_assert16@py_assert18@py_assert20@py_assert19@py_format22@py_format24s                       rR   test_queue_timeout_marks_failedrq    s   	5	6BH./I%.BJJ!"1


,89qCF9x*>>?#J"%
 		 \\"n,G 	[&&aXY&ZG	[  	NI 	Nq 	NIq) 	NGKK 	N 	N! 	NK!,D 	N),DD 	N 	NDI 	N 	N<M<M	ND 	N 	NGMv	N 	N5M5M  	N 	NDMI  	N 	NDMI  	N 	NDMI & 	N 	NDMI () 	N 	NDMI * 	N 	NGMv	N 	N5M5M -4 	N 	NDMI -4 	N 	NDMI -8 	N 	NDMI 9A 	N 	NDMI CD 	N 	NDMI -E 	N 	NDMI IJ 	N 	N<M<MCG9M	N 	N 	N:M:M	N 	N 	N 	N 	

)40) :	[ 	[ 	

)40s5   K J09K J5G2K 0K 5J?:K "K$c           
        g dfd}t        d      }t        | dz        }|t        j                  d<   	 |j	                  |D cg c]  }t        |       c}t        | dz        ddgddd	d
d       t        d|      5  	 dd
l}t        j                  |d|      5  |j                  ddd      }d
d
d
       d
d
d
        }|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
}j$                  }d}
d} ||
|      }d}||kD  }|st        j&                  d|fd||f      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        j                  d|       dz   d|iz  }t!        t        j"                  |            d
x}x}
x}x}x}}t        j                  j)                  dd
       y
c c}w # 1 sw Y   xY w# t        $ r |j                  ddd      }Y w xY w# 1 sw Y   xY w# t        j                  j)                  dd
       w xY w) u;  렌더링 및 큐 처리 중 외부 HTTP API 호출이 없는지 확인합니다 (§0.5).

    urllib.request.urlopen 과 requests.get 을 모킹하여 외부 호출 여부를 감지합니다.
    socket.create_connection은 ffmpeg 서브프로세스와의 충돌을 피하기 위해 모킹하지 않습니다.
    c                 <    j                  d       t        d      )NTuP   외부 네트워크 호출이 금지되어 있습니다 (urllib.request.urlopen))r5   r8   )r.  r&  external_callss     rR   mock_urlopenz7test_no_external_api_direct_calls.<locals>.mock_urlopen  s    d#oppr_   motion_render_queue_t11queue_no_netr  zno_net_output.mp4r]   r~   r   r  Nr  zurllib.request.urlopenrC  r   r\  r}   r  r	  u4   외부 네트워크 호출이 감지되었습니다!z
>assert not %(py0)sr   rt  r  r   rW  r  rX  u;   렌더링 성공이 기대되었으나 실패했습니다: rY  rZ  )r.  r3  r&  r3  r4  r5  )rW   r>   r  r  r  r   requestsr3  r  ImportErrorr/   r7   r1   r2   r3   r4   r8   r9   r\  r0   r  )rk   rt   ru  r  r  r  rx  r  rC   @py_format2rE   r   r   rG   r   r   r]  rt  s                    @rR   !test_no_external_api_direct_callsr{    sQ    "$Nq 
5	6BH~-.I%.BJJ!"1


,89qCF9x*==>#J"%
 	 +F 	``\\(E|L d ..aQSab.cGd	` "!Y!YY#YYYYYYY>YYY>YYYYYY{{u9uau{9a(u1u(1,uuu(1uuuuuuwuuuwuuu{uuu9uuuauuu(uuu1uuu0klskt.uuuuuuuu 	

)40+ :d d `**!R]^*_`	` 	` 	

)40sl   K J*K KJ+ J5J+=F;K K J(	#J++KKKKKK "K>c                    t         j                  t         d   } | j                  }| j                  }t        j                  t        d      5   |d       ddd       dddd	d
d|d<   	 t        j                  t        d      5   |d       ddd       dt        |j                               v r|d= yy# 1 sw Y   axY w# 1 sw Y   5xY w# dt        |j                               v r|d= w w xY w)ue   알 수 없는 트랙 ID와 허용되지 않은 라이선스에 대해 ValueError가 발생합니다.z.bgmu   알 수 없는 트랙r   nonexistent_track_xyzNzBad License TrackunknownPROPRIETARYzhttps://placeholder.example/badzbad_license.mp3)namesourcelicenseurlfilenametest_bad_license_tracku    허용되지 않은 라이선스)
r)   r*   r'   validate_licenseBGM_LIBRARYr   r   r   listr   )bgm_modr  r  s      rR   +test_bgm_license_validation_rejects_unknownr    s    kk/056G//%%K 
z)@	A 2012
 $ 0%-K()6]]:-OP 	756	7 $tK,<,<,>'??45 @#2 2	7 	7 $tK,<,<,>'??45 @s0   	B7+C 	CC 7C CC !C0)r4  r3  )motion_render_queue)rU   r>   r4  r3  )r4  tuple[int, int])rk   r   r`   r  r4  
list[Path])r   zpytest.TempPathFactoryr4  r   )r4  r5  )rx   r   r4  r5  )rx   r   rk   r   r4  r5  )rk   r   rt   r  r4  r5  )rk   r   rt   r  rK  zpytest.MonkeyPatchr4  r5  )8__doc__
__future__r   builtinsr1   _pytest.assertion.rewrite	assertionrewriter/   importlib.utilr+   importlib.machineryr  r   r   r)   r>  urllib.requesturllibwarningsconcurrent.futuresr   pathlibr   unittest.mockr   r   PILr   r(   r'   rS   rW   _ffmpeg_pathsr   r   r   markskipif_FFMPEG_SKIPfixturer`   rt   rx   r   r   r   r   r   r   r  rL  r^  rq  r{  r  r^   r_   rR   <module>r     sx    #      	   
    1    <
) '!T$   /9	&'..0 *v||HT)  {{!!&7"7@_!`  
   i  !D,SF v v4 + + 3 3. 1F31l!1H1>'1T6r_   