1+ import { OpenAiHandler } from '../openai'
2+ import { ApiHandlerOptions , openAiModelInfoSaneDefaults } from '../../../shared/api'
3+ import OpenAI , { AzureOpenAI } from 'openai'
4+ import { Anthropic } from '@anthropic-ai/sdk'
5+
6+ // Mock dependencies
7+ jest . mock ( 'openai' )
8+
9+ describe ( 'OpenAiHandler' , ( ) => {
10+ const mockOptions : ApiHandlerOptions = {
11+ openAiApiKey : 'test-key' ,
12+ openAiModelId : 'gpt-4' ,
13+ openAiStreamingEnabled : true ,
14+ openAiBaseUrl : 'https://api.openai.com/v1'
15+ }
16+
17+ beforeEach ( ( ) => {
18+ jest . clearAllMocks ( )
19+ } )
20+
21+ test ( 'constructor initializes with correct options' , ( ) => {
22+ const handler = new OpenAiHandler ( mockOptions )
23+ expect ( handler ) . toBeInstanceOf ( OpenAiHandler )
24+ expect ( OpenAI ) . toHaveBeenCalledWith ( {
25+ apiKey : mockOptions . openAiApiKey ,
26+ baseURL : mockOptions . openAiBaseUrl
27+ } )
28+ } )
29+
30+ test ( 'constructor initializes Azure client when Azure URL is provided' , ( ) => {
31+ const azureOptions : ApiHandlerOptions = {
32+ ...mockOptions ,
33+ openAiBaseUrl : 'https://example.azure.com' ,
34+ azureApiVersion : '2023-05-15'
35+ }
36+ const handler = new OpenAiHandler ( azureOptions )
37+ expect ( handler ) . toBeInstanceOf ( OpenAiHandler )
38+ expect ( AzureOpenAI ) . toHaveBeenCalledWith ( {
39+ baseURL : azureOptions . openAiBaseUrl ,
40+ apiKey : azureOptions . openAiApiKey ,
41+ apiVersion : azureOptions . azureApiVersion
42+ } )
43+ } )
44+
45+ test ( 'getModel returns correct model info' , ( ) => {
46+ const handler = new OpenAiHandler ( mockOptions )
47+ const result = handler . getModel ( )
48+
49+ expect ( result ) . toEqual ( {
50+ id : mockOptions . openAiModelId ,
51+ info : openAiModelInfoSaneDefaults
52+ } )
53+ } )
54+
55+ test ( 'createMessage handles streaming correctly when enabled' , async ( ) => {
56+ const handler = new OpenAiHandler ( {
57+ ...mockOptions ,
58+ openAiStreamingEnabled : true ,
59+ includeMaxTokens : true
60+ } )
61+
62+ const mockStream = {
63+ async * [ Symbol . asyncIterator ] ( ) {
64+ yield {
65+ choices : [ {
66+ delta : {
67+ content : 'test response'
68+ }
69+ } ] ,
70+ usage : {
71+ prompt_tokens : 10 ,
72+ completion_tokens : 5
73+ }
74+ }
75+ }
76+ }
77+
78+ const mockCreate = jest . fn ( ) . mockResolvedValue ( mockStream )
79+ ; ( OpenAI as jest . MockedClass < typeof OpenAI > ) . prototype . chat = {
80+ completions : { create : mockCreate }
81+ } as any
82+
83+ const systemPrompt = 'test system prompt'
84+ const messages : Anthropic . Messages . MessageParam [ ] = [
85+ { role : 'user' , content : 'test message' }
86+ ]
87+
88+ const generator = handler . createMessage ( systemPrompt , messages )
89+ const chunks = [ ]
90+
91+ for await ( const chunk of generator ) {
92+ chunks . push ( chunk )
93+ }
94+
95+ expect ( chunks ) . toEqual ( [
96+ {
97+ type : 'text' ,
98+ text : 'test response'
99+ } ,
100+ {
101+ type : 'usage' ,
102+ inputTokens : 10 ,
103+ outputTokens : 5
104+ }
105+ ] )
106+
107+ expect ( mockCreate ) . toHaveBeenCalledWith ( {
108+ model : mockOptions . openAiModelId ,
109+ messages : [
110+ { role : 'system' , content : systemPrompt } ,
111+ { role : 'user' , content : 'test message' }
112+ ] ,
113+ temperature : 0 ,
114+ stream : true ,
115+ stream_options : { include_usage : true } ,
116+ max_tokens : openAiModelInfoSaneDefaults . maxTokens
117+ } )
118+ } )
119+
120+ test ( 'createMessage handles non-streaming correctly when disabled' , async ( ) => {
121+ const handler = new OpenAiHandler ( {
122+ ...mockOptions ,
123+ openAiStreamingEnabled : false
124+ } )
125+
126+ const mockResponse = {
127+ choices : [ {
128+ message : {
129+ content : 'test response'
130+ }
131+ } ] ,
132+ usage : {
133+ prompt_tokens : 10 ,
134+ completion_tokens : 5
135+ }
136+ }
137+
138+ const mockCreate = jest . fn ( ) . mockResolvedValue ( mockResponse )
139+ ; ( OpenAI as jest . MockedClass < typeof OpenAI > ) . prototype . chat = {
140+ completions : { create : mockCreate }
141+ } as any
142+
143+ const systemPrompt = 'test system prompt'
144+ const messages : Anthropic . Messages . MessageParam [ ] = [
145+ { role : 'user' , content : 'test message' }
146+ ]
147+
148+ const generator = handler . createMessage ( systemPrompt , messages )
149+ const chunks = [ ]
150+
151+ for await ( const chunk of generator ) {
152+ chunks . push ( chunk )
153+ }
154+
155+ expect ( chunks ) . toEqual ( [
156+ {
157+ type : 'text' ,
158+ text : 'test response'
159+ } ,
160+ {
161+ type : 'usage' ,
162+ inputTokens : 10 ,
163+ outputTokens : 5
164+ }
165+ ] )
166+
167+ expect ( mockCreate ) . toHaveBeenCalledWith ( {
168+ model : mockOptions . openAiModelId ,
169+ messages : [
170+ { role : 'user' , content : systemPrompt } ,
171+ { role : 'user' , content : 'test message' }
172+ ]
173+ } )
174+ } )
175+
176+ test ( 'createMessage handles API errors' , async ( ) => {
177+ const handler = new OpenAiHandler ( mockOptions )
178+ const mockStream = {
179+ async * [ Symbol . asyncIterator ] ( ) {
180+ throw new Error ( 'API Error' )
181+ }
182+ }
183+
184+ const mockCreate = jest . fn ( ) . mockResolvedValue ( mockStream )
185+ ; ( OpenAI as jest . MockedClass < typeof OpenAI > ) . prototype . chat = {
186+ completions : { create : mockCreate }
187+ } as any
188+
189+ const generator = handler . createMessage ( 'test' , [ ] )
190+ await expect ( generator . next ( ) ) . rejects . toThrow ( 'API Error' )
191+ } )
192+ } )
0 commit comments