
    (<i)/                        d Z ddlZddlZddlZddlmZ ddlmZmZ ddl	Z	ej                  j                  d e ee      j                  j                  j                               	 ddlmZ ddlmZmZmZ dZe	j.                  j1                  e d	
      Z G d d      Z G d d      Ze	j8                  d        Z G d d      Z G d d      Z G d d      Z  G d d      Z!y# e$ r dZY vw xY w)u   services/openai_compat_server.py 테스트 스위트

FastAPI TestClient를 사용한 엔드포인트 통합 테스트 +
Pydantic 모델 단위 테스트.
    N)Path)AnyDict)
TestClient)appChatCompletionRequestChatCompletionResponseTFzfastapi not installed)reasonc                   0    e Zd ZdZddZddZddZddZy)TestChatCompletionRequestu/   ChatCompletionRequest Pydantic 모델 테스트Nc                 |    t        ddddg      }|j                  dk(  sJ t        |j                        dk(  sJ y)u   기본 필드로 생성gpt-4ouserHellorolecontentmodelmessages   N)r   r   lenr   selfreqs     Y/home/jay/workspace/.worktrees/task-2057-dev2/services/tests/test_openai_compat_server.pytest_basic_creationz-TestChatCompletionRequest.test_basic_creation-   sE    #%':;
 yyH$$$3<< A%%%    c                 6   t        ddddg      }|j                  t        |j                  t              sJ |j                  t        |j                  t
              sJ |j                  du s*|j                  t        |j                  t              sJ yyy)u   선택 필드 기본값 확인claude-sonnet-4-6r   hir   r   NF)r   temperature
isinstancefloat
max_tokensintstreamboolr   s     r   test_defaultsz'TestChatCompletionRequest.test_defaults6   s    #%%$78
 &*S__e*LLL~~%CNNC)HHHzzU"cjj&8JszzSW<XXX<X&8"r   c                     t        dddddddgddd	
      }|j                  dk(  sJ |j                  dk(  sJ |j                  d	u sJ y)u   모든 필드 설정zgpt-4o-minisystemzYou are helpful.r   r   zSummarize this.gffffff?i   F)r   r   r"   r%   r'   N)r   r"   r%   r'   r   s     r   test_with_all_fieldsz.TestChatCompletionRequest.test_with_all_fields@   si    #!.@A,=> 	
 #%%%~~$$$zzU"""r   c                 H    t        ddddgd      }|j                  du sJ y)u   stream=True 설정r   r   r!   r   Tr   r   r'   N)r   r'   r   s     r   test_stream_truez*TestChatCompletionRequest.test_stream_trueP   s1    #%$78

 zzT!!!r   returnN)__name__
__module____qualname____doc__r   r)   r,   r/    r   r   r   r   *   s    9&Y# "r   r   c                        e Zd ZdZddZddZy)TestChatCompletionResponseu0   ChatCompletionResponse Pydantic 모델 테스트Nc           
          t        ddt        t        j                               ddddddd	g
      }|j                  j	                  d      sJ |j
                  dk(  sJ t        |j                        dk(  sJ y)u"   OpenAI 형식 필드 구조 확인zchatcmpl-test-123chat.completionr   r   	assistantzHello!r   stop)indexmessagefinish_reasonidobjectcreatedr   choices	chatcmpl-r   N)r	   r&   timerA   
startswithrB   r   rD   r   resps     r   test_openai_format_fieldsz4TestChatCompletionResponse.test_openai_format_fields]   s    %"$		$ (3I%+
 ww!!+...{{////4<< A%%%r   c                 F    t        ddddg       }|j                  dk(  sJ y)u   usage 필드는 선택적zchatcmpl-abcr:   iIr    r@   N)r	   rA   rH   s     r   test_usage_field_optionalz4TestChatCompletionResponse.test_usage_field_optionalp   s/    %$%
 ww.(((r   r0   )r2   r3   r4   r5   rJ   rL   r6   r   r   r8   r8   Z   s    :&&
)r   r8   c                       t        t              S )zFastAPI TestClient fixture)r   r   r6   r   r   clientrN      s     c?r   c                        e Zd ZdZddZddZy)TestHealthEndpointu   GET /health 테스트Nc                 H    |j                  d      }|j                  dk(  sJ y)u   상태 확인 - 200 OK/health   Ngetstatus_coder   rN   responses      r   test_health_returns_200z*TestHealthEndpoint.test_health_returns_200   s$    ::i(##s***r   c                 f    |j                  d      }|j                         }d|v sJ |d   dk(  sJ y)u,   health 응답 본문에 status 필드 포함rR   statusokNrU   jsonr   rN   rX   bodys       r   test_health_response_bodyz,TestHealthEndpoint.test_health_response_body   s;    ::i(}}4H~%%%r   r0   )r2   r3   r4   r5   rY   ra   r6   r   r   rP   rP      s    +
&r   rP   c                   0    e Zd ZdZddZddZddZddZy)TestModelsEndpointu   GET /v1/models 테스트Nc                 H    |j                  d      }|j                  dk(  sJ y)u   모델 목록 - 200 OK
/v1/modelsrS   NrT   rW   s      r   test_models_returns_200z*TestModelsEndpoint.test_models_returns_200   s$    ::l+##s***r   c                     |j                  d      }|j                         }d|v sJ |d   dk(  sJ d|v sJ t        |d   t              sJ y)u"   OpenAI 형식 응답 구조 확인re   rB   listdataN)rU   r^   r#   rh   r_   s       r   test_models_response_formatz.TestModelsEndpoint.test_models_response_format   s[    ::l+}}4H~'''~~$v,---r   c                 l    |j                  d      }|j                         }t        |d         dkD  sJ y)u$   모델 목록이 비어있지 않음re   ri   r   N)rU   r^   r   r_   s       r   test_models_list_not_emptyz-TestModelsEndpoint.test_models_list_not_empty   s2    ::l+}}4< 1$$$r   c                 f    |j                  d      }|j                         }|d   D ]  }d|v rJ  y)u%   각 모델 항목에 id 필드 존재re   ri   rA   Nr]   )r   rN   rX   r`   model_entrys        r   test_model_entry_has_idz*TestModelsEndpoint.test_model_entry_has_id   s<    ::l+}}< 	'K;&&&	'r   r0   )r2   r3   r4   r5   rf   rj   rl   ro   r6   r   r   rc   rc      s    "+
.%'r   rc   c                       e Zd ZdZdeeef   fdZddZddZ	ddZ
ddZdd	Zdd
ZddZddZddZddZddZddZddZddZy)TestChatCompletionsEndpointu#   POST /v1/chat/completions 테스트r1   c                     ddddgdS )Nr   r   r   r   r   r6   )r   s    r   _basic_payloadz*TestChatCompletionsEndpoint._basic_payload   s    "(W=>
 	
r   Nc                 h    |j                  d| j                               }|j                  dk(  sJ y)u   기본 요청 - 200 OK/v1/chat/completionsr^   rS   N)postrs   rV   rW   s      r   test_completions_returns_200z8TestChatCompletionsEndpoint.test_completions_returns_200   s2    ;;5D<O<O<Q;R##s***r   c                     |j                  d| j                               }|j                         }d|v sJ |d   j                  d      sJ y)u   응답에 id 필드 포함ru   rv   rA   rE   N)rw   rs   r^   rG   r_   s       r    test_completions_response_has_idz<TestChatCompletionsEndpoint.test_completions_response_has_id   sM    ;;5D<O<O<Q;R}}t||Dz$$[111r   c                     |j                  d| j                               }|j                         }|j                  d      dk(  sJ y)u'   응답 object 필드가 chat.completionru   rv   rB   r:   N)rw   rs   r^   rU   r_   s       r   %test_completions_response_object_typezATestChatCompletionsEndpoint.test_completions_response_object_type   sA    ;;5D<O<O<Q;R}}xx!%6666r   c                     |j                  d| j                               }|j                         }d|v sJ t        |d   t              sJ t        |d         dkD  sJ y)u   응답에 choices 배열 포함ru   rv   rD   r   N)rw   rs   r^   r#   rh   r   r_   s       r   %test_completions_response_has_choiceszATestChatCompletionsEndpoint.test_completions_response_has_choices   sb    ;;5D<O<O<Q;R}}D   $y/40004	?#a'''r   c                     |j                  d| j                               }|j                         }|d   d   }d|v sJ d|d   v sJ |d   d   dk(  sJ y)	u#   choices[0]에 message 필드 포함ru   rv   rD   r   r>   r   r;   Nrw   rs   r^   r   rN   rX   r`   choices        r   #test_completions_choice_has_messagez?TestChatCompletionsEndpoint.test_completions_choice_has_message   sp    ;;5D<O<O<Q;R}}i#F"""	****i (K777r   c                 r    |j                  d| j                               }|j                         }d|v sJ y)u   응답에 model 필드 포함ru   rv   r   Nr   r_   s       r   #test_completions_response_has_modelz?TestChatCompletionsEndpoint.test_completions_response_has_model   s5    ;;5D<O<O<Q;R}}$r   c                     |j                  d| j                               }|j                         }d|v sJ t        |d   t              sJ y)u(   응답에 created 타임스탬프 포함ru   rv   rC   N)rw   rs   r^   r#   r&   r_   s       r   %test_completions_response_has_createdzATestChatCompletionsEndpoint.test_completions_response_has_created   sK    ;;5D<O<O<Q;R}}D   $y/3///r   c                 P    |j                  dddi      }|j                  dk(  sJ y)u1   messages 없는 요청 - 422 Unprocessable Entityru   r   r   rv     Nrw   rV   rW   s      r   1test_completions_invalid_request_missing_messageszMTestChatCompletionsEndpoint.test_completions_invalid_request_missing_messages   s5    ;;"'8)<  
 ##s***r   c                 X    |j                  dddddgi      }|j                  dk(  sJ y)	u   model 없는 요청 - 422ru   r   r   r!   r   rv   r   Nr   rW   s      r   .test_completions_invalid_request_missing_modelzJTestChatCompletionsEndpoint.test_completions_invalid_request_missing_model   s>    ;;"4@AB  
 ##s***r   c                 f    dddddddgd}|j                  d|	      }|j                  d
k(  sJ y)u   system + user 메시지 처리r   r+   zYou are a helpful assistant.r   r   zWhat is 2+2?r   ru   rv   rS   Nr   r   rN   payloadrX   s       r   $test_completions_with_system_messagez@TestChatCompletionsEndpoint.test_completions_with_system_message   sM     !.LMN;
 ;;5G;D##s***r   c                 x    i | j                         ddi}|j                  d|      }|j                  dk(  sJ y)u   temperature 파라미터 처리r"   g      ?ru   rv   rS   Nrs   rw   rV   r   s       r   !test_completions_with_temperaturez=TestChatCompletionsEndpoint.test_completions_with_temperature  sC    ?T((*?M3?;;5G;D##s***r   c                 x    i | j                         ddi}|j                  d|      }|j                  dk(  sJ y)u   max_tokens 파라미터 처리r%   d   ru   rv   rS   Nr   r   s       r    test_completions_with_max_tokensz<TestChatCompletionsEndpoint.test_completions_with_max_tokens  sC    >T((*>L#>;;5G;D##s***r   c                     |j                  d| j                               }|j                         }|d   d   }d|v sJ y)u   choices[0].finish_reason 존재ru   rv   rD   r   r?   Nr   r   s        r   &test_completions_finish_reason_presentzBTestChatCompletionsEndpoint.test_completions_finish_reason_present  sE    ;;5D<O<O<Q;R}}i#&(((r   c                     |j                  d| j                               }|j                         }|d   d   d   dk(  sJ y)zchoices[0].index == 0ru   rv   rD   r   r=   Nr   r_   s       r   test_completions_choice_indexz9TestChatCompletionsEndpoint.test_completions_choice_index  sE    ;;5D<O<O<Q;R}}Iq!'*a///r   r0   )r2   r3   r4   r5   r   strr   rs   rx   rz   r|   r~   r   r   r   r   r   r   r   r   r   r   r6   r   r   rq   rq      s_    -
S#X 
+
27(80++
+++)0r   rq   c                   0    e Zd ZdZddZddZddZddZy)TestStreamEndpointu3   POST /v1/chat/completions stream=True SSE 테스트Nc                     ddddgdd}|j                  dd|	      5 }|j                  d
k(  sJ 	 ddd       y# 1 sw Y   yxY w)u   stream=True 요청 - 200 OKr   r   r!   r   Tr.   POSTru   rv   rS   N)r'   rV   r   s       r   test_stream_returns_200z*TestStreamEndpoint.test_stream_returns_200(  s\     "(T:;

 ]]6#9]H 	/H''3...	/ 	/ 	/s	   ;Ac                     ddddgdd}|j                  dd|	      5 }|j                  j                  d
d      }d|v sJ 	 ddd       y# 1 sw Y   yxY w)u4   stream=True 응답 Content-Type이 text/event-streamr   r   r!   r   Tr.   r   ru   rv   zcontent-type ztext/event-streamN)r'   headersrU   )r   rN   r   rX   content_types        r   %test_stream_content_type_event_streamz8TestStreamEndpoint.test_stream_content_type_event_stream2  sn     "(T:;

 ]]6#9]H 	7H#++//CL&,666	7 	7 	7s   #AAc                     ddddgdd}|j                  dd|	      5 }t        |j                               }d
j                  |      }d|v sJ 	 ddd       y# 1 sw Y   yxY w)u$   SSE 스트림에 data: 라인 포함r   r   r!   r   Tr.   r   ru   rv   r   zdata:Nr'   rh   	iter_textjoinr   rN   r   rX   chunkstexts         r   test_stream_contains_data_linesz2TestStreamEndpoint.test_stream_contains_data_lines=  st     "(T:;

 ]]6#9]H 	#H(,,./F776?Dd?"?	# 	# 	#   1AA#c                     ddddgdd}|j                  dd|	      5 }t        |j                               }d
j                  |      }d|v sJ 	 ddd       y# 1 sw Y   yxY w)u%   SSE 스트림이 [DONE] 으로 종료r   r   r!   r   Tr.   r   ru   rv   r   z[DONE]Nr   r   s         r   test_stream_ends_with_donez-TestStreamEndpoint.test_stream_ends_with_doneI  sv     "(T:;

 ]]6#9]H 	$H(,,./F776?Dt###	$ 	$ 	$r   r0   )r2   r3   r4   r5   r   r   r   r   r6   r   r   r   r   %  s    =/	7
#
$r   r   )"r5   r^   sysrF   pathlibr   typingr   r   pytestpathinsertr   __file__parentfastapi.testclientr   services.openai_compat_serverr   r   r	   FASTAPI_AVAILABLEImportErrormarkskipif
pytestmarkr   r8   fixturerN   rP   rc   rq   r   r6   r   r   <module>r      s     
     3tH~,,33::; <	- 
 
 [["   
-" -"` )  )P  
& & ' '>k0 k0\.$ .$U  s   (C' 'C10C1