
    iM0                         d Z ddlZddlmZmZmZ ddlZddlZdeee	      defdZ
ddedee	   f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y)uP   
Tests for embedding_service.py

TDD: 테스트 먼저 작성, 구현은 이후
    N)	MagicMockcallpatchvectorsreturnc                 b    t               }| D cg c]  }t        |       c}|_        |S c c}w )uI   openai embeddings.create() 반환값을 흉내 내는 mock 객체 생성.)	embedding)r   data)r   responsevecs      R/home/jay/workspace/.worktrees/task-2116-dev1/libs/tests/test_embedding_service.py_make_embedding_responser      s+    {H9@A#Y-AHMO Bs   ,dimc                     dg| z  S )u#   dim 차원의 더미 벡터 반환.g? )r   s    r   _make_vectorr      s    53;    T)autousec                 (    | j                  dd       y)u0   모든 테스트에서 OPENAI_API_KEY를 설정.OPENAI_API_KEYztest-api-keyN)setenv)monkeypatchs    r   set_api_keyr   #   s     '8r   c                   "    e Zd ZdZd Zd Zd Zy)TestGetEmbeddingu5   get_embedding() 단일 텍스트 임베딩 테스트.c                 &   ddl m} t        t        d      g      }t	        d      5 }t               }||_        ||j                  j                  _         |d      }ddd       t        t              sJ t        |      dk(  sJ y# 1 sw Y   ,xY w)u>   단일 텍스트 입력 시 1536차원 벡터를 반환한다.r   get_embedding   embedding_service.openai.OpenAIzhello worldN)embedding_servicer   r   r   r   r   return_value
embeddingscreate
isinstancelistlenselfr   mock_responsemock_client_clsmock_clientresults         r   test_returns_1536_dim_vectorz-TestGetEmbedding.test_returns_1536_dim_vector1   s    30,t2D1EF45 	2#+K+6O(9FK""))6"=1F	2 &$'''6{d"""	2 	2s   5BBc                    ddl m} t        t        d      g      }t	        d      5 }t               }||_        ||j                  j                  _         |d      }ddd       t              dk(  sJ y# 1 sw Y   xY w)u4   반환 벡터 길이가 정확히 1536인지 검증.r   r   r   r    z	test textN)
r!   r   r   r   r   r   r"   r#   r$   r'   r(   s         r   test_vector_length_is_1536z+TestGetEmbedding.test_vector_length_is_1536A   sx    30,t2D1EF45 	0#+K+6O(9FK""))6";/F	0 6{d"""	0 	0s   5A55A>c                 4   ddl m} t        t        d      g      }t	        d      5 }t               }||_        ||j                  j                  _         |d       |j                  j                  j                  dgdd       ddd       y# 1 sw Y   yxY w)	uF   올바른 model과 dimensions 인자로 API를 호출하는지 검증.r   r   r   r    zsample textztext-embedding-3-small)inputmodel
dimensionsN)
r!   r   r   r   r   r   r"   r#   r$   assert_called_once_with)r)   r   r*   r+   r,   s        r   0test_calls_api_with_correct_model_and_dimensionszATestGetEmbedding.test_calls_api_with_correct_model_and_dimensionsP   s    30,t2D1EF45 	#+K+6O(9FK""))6-(""))AA$o. B 	 	 	s   ABBN)__name__
__module____qualname____doc__r.   r0   r6   r   r   r   r   r   .   s    ?# #r   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetEmbeddingsBatchu2   get_embeddings_batch() 배치 임베딩 테스트.c                 @   ddl m} g d}|D cg c]  }t                }}t        |      }t	        d      5 }t               }||_        ||j                  j                  _         ||      }ddd       t              t        |      k(  sJ yc c}w # 1 sw Y   (xY w)u@   입력 텍스트 수와 동일한 수의 벡터를 반환한다.r   get_embeddings_batch)text1text2text3r    N)
r!   r?   r   r   r   r   r"   r#   r$   r'   )	r)   r?   texts_r   r*   r+   r,   r-   s	            r   ,test_returns_same_number_of_vectors_as_inputzCTestGetEmbeddingsBatch.test_returns_same_number_of_vectors_as_inputg   s    :++01a<>110945 	1#+K+6O(9FK""))6)%0F	1 6{c%j((( 2	1 	1s   B5BBc                     ddl m} t        d      5 }t               }||_         |g       }ddd       g k(  sJ j
                  j                  j                          y# 1 sw Y   5xY w)uK   빈 리스트 입력 시 빈 리스트를 반환한다 (API 호출 없음).r   r>   r    N)r!   r?   r   r   r"   r#   r$   assert_not_called)r)   r?   r+   r,   r-   s        r   "test_empty_list_returns_empty_listz9TestGetEmbeddingsBatch.test_empty_list_returns_empty_listx   sd    :45 	.#+K+6O()"-F		. ||%%779	. 	.s   A  A)c                    ddl m} t        d      D cg c]  }d| 	 }}t        t        d      D cg c]  }t	                c}      }t        t        d      D cg c]  }t	                c}      }t        t        d      D cg c]  }t	                c}      }t        d      5 }t               }	|	|_        |||g|	j                  j                  _
         ||      }
ddd       	j                  j                  j                  d	k(  sJ t        
      dk(  sJ yc c}w c c}w c c}w c c}w # 1 sw Y   SxY w)
uY   100개 초과 텍스트 입력 시 100개씩 분할하여 여러 번 API를 호출한다.r   r>      text_d   2   r    N   )r!   r?   ranger   r   r   r   r"   r#   r$   side_effect
call_countr'   )r)   r?   irC   rD   batch1_responsebatch2_responsebatch3_responser+   r,   r-   s              r   'test_over_100_texts_splits_into_batchesz>TestGetEmbeddingsBatch.test_over_100_texts_splits_into_batches   s   :&+Cj1511 3ERUJ3WqLN3WX2ERUJ3WqLN3WX2ERTI3VqLN3VW45 		1#+K+6O(9K""))5 *%0F		1 %%,,771<<<6{c!!!+ 2 4X3W3V		1 		1s"   D!D&D+D0+8D55D>c                 `   ddl m} t        d      D cg c]  }d| 	 }}t        t        d      D cg c]  }t	                c}      }t        t        d      D cg c]  }t	                c}      }t        d      5 }t               }||_        ||g|j                  j                  _
         ||       ddd       j                  j                  j                  }	|	d   j                  d	   |dd k(  sJ |	d
   j                  d	   |dd k(  sJ yc c}w c c}w c c}w # 1 sw Y   mxY w)uM   배치 분할 시 각 호출의 input이 올바른 슬라이스인지 검증.r   r>      trL   rM   r    Nr2      )r!   r?   rO   r   r   r   r   r"   r#   r$   rP   call_args_listkwargs)
r)   r?   rR   rC   rD   rS   rT   r+   r,   callss
             r   +test_batch_split_calls_correct_input_sliceszBTestGetEmbeddingsBatch.test_batch_split_calls_correct_input_slices   s   :"'*-Q1QC--2ERUJ3WqLN3WX2ERTI3VqLN3VW45 	(#+K+6O(9K""))5
 !'	( &&--<<Qxw'5#;666Qxw'5;666# .3W3V	( 	(s   DDD7D$$D-N)r7   r8   r9   r:   rE   rH   rV   r^   r   r   r   r<   r<   d   s    <)":"67r   r<   c                   "    e Zd ZdZd Zd Zd Zy)TestRetryLogicu*   API 에러 시 재시도 로직 테스트.c                    ddl m} t        j                  dt	               d      }t        d      5 }t	               }||_        ||j                  j                  _	        t        d      5  t        j                  t        j                        5   |d       ddd       ddd       ddd       j                  j                  j                  d	k(  sJ y# 1 sw Y   ?xY w# 1 sw Y   CxY w# 1 sw Y   GxY w)
u7   openai.APIError 발생 시 최대 3회 재시도한다.r   r   zInternal Server ErrorNmessagerequestbodyr    embedding_service.time.sleeptestrN   )r!   r   openaiAPIErrorr   r   r"   r#   r$   rP   pytestraisesrQ   r)   r   	api_errorr+   r,   s        r   !test_retries_3_times_on_api_errorz0TestRetryLogic.test_retries_3_times_on_api_error   s    3OO+K
	 45 	*#+K+6O(8AK""))556 *]]6??3 *!&)**	* %%,,771<<<	* ** *	* 	*s;   8C-*$C!	CC!C-CC!!C*	&C--C6c                    ddl m} t        j                  dt	               d      }t        d      5 }t	               }||_        ||j                  j                  _	        t        d      5  t        j                  t        j                        5   |d       ddd       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   yxY w)	u4   3회 모두 실패 시 원래 에러를 raise한다.r   r   zServer ErrorNrb   r    rf   rg   )r!   r   rh   ri   r   r   r"   r#   r$   rP   rj   rk   rl   s        r   "test_raises_after_3_failed_retriesz1TestRetryLogic.test_raises_after_3_failed_retries   s    3OO"K
	 45 	*#+K+6O(8AK""))556 *]]6??3 *!&)**	* 	** ** *	* 	*s;   8C*$B<	B0B<C0B95B<<C	CCc                    ddl m} t        j                  dt	               d      }t        t        d      g      }t        d      5 }t	               }||_        ||g|j                  j                  _        t        d      5   |d	      }ddd       ddd       t              dk(  sJ j                  j                  j                  d
k(  sJ y# 1 sw Y   GxY w# 1 sw Y   KxY w)uY   첫 번째 시도 실패 후 두 번째 시도에서 성공하면 결과를 반환한다.r   r   zTemporary ErrorNrb   r   r    rf   rg      )r!   r   rh   ri   r   r   r   r   r"   r#   r$   rP   r'   rQ   )r)   r   rm   success_responser+   r,   r-   s          r   /test_succeeds_on_second_attempt_after_api_errorz>TestRetryLogic.test_succeeds_on_second_attempt_after_api_error   s    3OO%K
	
 4\$5G4HI45 	/#+K+6O(9BDT8UK""))556 /&v./	/ 6{d"""%%,,771<<<	/ /	/ 	/s$   :C	C
CC	CC%N)r7   r8   r9   r:   rn   rp   rt   r   r   r   r`   r`      s    4=,*&=r   r`   c                       e Zd ZdZd Zd Zy)TestRateLimitHandlingu(   Rate limit 에러(429) 처리 테스트.c                    ddl m} t        j                  dt	        di       d      }t        t        d      g      }t        d	      5 }t	               }||_        ||g|j                  j                  _        t        d
      5 } |d      }ddd       ddd       j                  dk\  sJ t              dk(  sJ y# 1 sw Y   3xY w# 1 sw Y   7xY w)u<   RateLimitError 발생 시 대기(sleep) 후 재시도한다.r   r   Rate limit exceeded  status_codeheadersNrc   r   re   r   r    rf   rg   rZ   )r!   r   rh   RateLimitErrorr   r   r   r   r"   r#   r$   rP   rQ   r'   )r)   r   rate_limit_errorrs   r+   r,   
mock_sleepr-   s           r   'test_rate_limit_error_waits_and_retriesz=TestRateLimitHandling.test_rate_limit_error_waits_and_retries   s    3!00)3;

 4\$5G4HI45 		/#+K+6O(  9K""))5
 56 /*&v./		/ $$)))6{d"""/ /		/ 		/s$   
:C	B?C?C	CCc                    ddl m} t        j                  dt	        di       d      }t        t        d      g      }t        d	      5 }t	               }||_        |||g|j                  j                  _        t        d
      5 } |d      }ddd       ddd       j                  D cg c]  }|j                  d    }	}|	d   dk(  sJ |	d   dk(  sJ t              dk(  sJ y# 1 sw Y   \xY w# 1 sw Y   `xY wc c}w )uN   RateLimitError 연속 발생 시 지수 백오프(1, 2, 4초)를 사용한다.r   r   rx   ry   rz   Nr}   r   r    rf   rg   rZ   rr   )r!   r   rh   r~   r   r   r   r   r"   r#   r$   rP   r[   argsr'   )
r)   r   r   rs   r+   r,   r   r-   csleep_callss
             r   (test_rate_limit_uses_exponential_backoffz>TestRateLimitHandling.test_rate_limit_uses_exponential_backoff  s   3!00)3;

 4\$5G4HI45 	/#+K+6O( !  9K""))5 56 /*&v./	/ +5*C*CDQqvvayDD1~"""1~"""6{d"""/ /	/ 	/ Es*   
;C5	C)C5,D)C2	.C55C>N)r7   r8   r9   r:   r   r   r   r   r   rv   rv      s    2#6#r   rv   c                       e Zd ZdZd Zd Zy)TestApiKeyValidationu&   API 키 환경변수 검증 테스트.c                     ddl m} |j                  dd       t        j                  t
        d      5   |d       ddd       y# 1 sw Y   yxY w)	uG   OPENAI_API_KEY 환경변수가 없으면 ValueError를 발생시킨다.r   r   r   Fraisingmatchrg   N)r!   r   delenvrj   rk   
ValueError)r)   r   r   s      r   ,test_raises_value_error_when_api_key_missingzATestApiKeyValidation.test_raises_value_error_when_api_key_missing;  sE    3+U;]]:-=> 	"&!	" 	" 	"s   	AAc                     ddl m} |j                  dd       t        j                  t
        d      5   |dg       ddd       y# 1 sw Y   yxY w)	uO   OPENAI_API_KEY 없으면 get_embeddings_batch도 ValueError를 발생시킨다.r   r>   r   Fr   r   textN)r!   r?   r   rj   rk   r   )r)   r   r?   s      r   6test_raises_value_error_for_batch_when_api_key_missingzKTestApiKeyValidation.test_raises_value_error_for_batch_when_api_key_missingD  sG    :+U;]]:-=> 	+ &*	+ 	+ 	+s   
AAN)r7   r8   r9   r:   r   r   r   r   r   r   r   8  s    0"+r   r   )r   )r:   osunittest.mockr   r   r   rh   rj   r&   floatr   intr   fixturer   r   r<   r`   rv   r   r   r   r   <module>r      s    
 0 0  d4;&7 I c T%[  9 93 3lQ7 Q7h@= @=F:# :#z+ +r   