12
12
import static org .mockito .Mockito .verify ;
13
13
import static org .mockito .Mockito .when ;
14
14
15
+ import java .lang .reflect .InvocationHandler ;
16
+ import java .lang .reflect .Method ;
17
+ import java .lang .reflect .Proxy ;
15
18
import java .net .http .HttpClient ;
16
19
import java .net .http .HttpRequest ;
17
20
import java .net .http .HttpResponse ;
18
21
import java .util .List ;
19
22
20
- import org .junit .jupiter .api .BeforeEach ;
21
- import org .junit .jupiter .api .Nested ;
22
23
import org .junit .jupiter .api .Test ;
23
24
import org .mockito .ArgumentCaptor ;
24
25
25
26
import io .github .sashirestela .cleverclient .CleverClient ;
26
- import io .github .sashirestela .cleverclient .http .HttpProcessor ;
27
- import io .github .sashirestela .cleverclient .util .ReflectUtil ;
28
27
import io .github .sashirestela .openai .domain .chat .ChatRequest ;
29
28
30
29
class SimpleOpenAITest {
31
30
32
31
HttpClient httpClient = mock (HttpClient .class );
33
32
CleverClient cleverClient = mock (CleverClient .class );
34
33
35
- @ Nested
36
- class StandAloneTests {
34
+ @ Test
35
+ void shouldSetPropertiesToDefaultValuesWhenBuilderIsCalledWithoutThoseProperties () {
36
+ var openAI = SimpleOpenAI .builder ()
37
+ .apiKey ("apiKey" )
38
+ .build ();
39
+ assertEquals (HttpClient .Version .HTTP_2 , openAI .getHttpClient ().version ());
40
+ assertEquals (OPENAI_BASE_URL , openAI .getBaseUrl ());
41
+ assertNotNull (openAI .getCleverClient ());
42
+ }
37
43
38
- @ Test
39
- void shouldSetPropertiesToDefaultValuesWhenBuilderIsCalledWithoutThoseProperties () {
40
- var openAI = SimpleOpenAI .builder ()
41
- .apiKey ("apiKey" )
42
- .build ();
43
- assertEquals (HttpClient .Version .HTTP_2 , openAI .getHttpClient ().version ());
44
- assertEquals (OPENAI_BASE_URL , openAI .getBaseUrl ());
45
- assertNotNull (openAI .getCleverClient ());
46
- }
44
+ @ Test
45
+ void shouldSetPropertiesWhenBuilderIsCalledWithThoseProperties () {
46
+ var otherUrl = "https://openai.com/api" ;
47
+ var openAI = SimpleOpenAI .builder ()
48
+ .apiKey ("apiKey" )
49
+ .baseUrl (otherUrl )
50
+ .httpClient (httpClient )
51
+ .build ();
52
+ assertEquals ("apiKey" , openAI .getApiKey ());
53
+ assertEquals (otherUrl , openAI .getBaseUrl ());
54
+ assertEquals (httpClient , openAI .getHttpClient ());
55
+ }
47
56
48
- @ Test
49
- void shouldSetPropertiesWhenBuilderIsCalledWithThoseProperties () {
50
- var otherUrl = "https://openai.com/api" ;
51
- var openAI = SimpleOpenAI .builder ()
52
- .apiKey ("apiKey" )
53
- .baseUrl (otherUrl )
54
- .httpClient (httpClient )
55
- .build ();
56
- assertEquals ("apiKey" , openAI .getApiKey ());
57
- assertEquals (otherUrl , openAI .getBaseUrl ());
58
- assertEquals (httpClient , openAI .getHttpClient ());
59
- }
57
+ @ Test
58
+ void shouldNotAddOrganizationToHeadersWhenBuilderIsCalledWithoutOrganizationId () {
59
+ var openAI = SimpleOpenAI .builder ()
60
+ .apiKey ("apiKey" )
61
+ .build ();
62
+ assertFalse (openAI .getCleverClient ().getHeaders ().containsValue (openAI .getOrganizationId ()));
63
+ }
60
64
61
- @ Test
62
- void shouldNotAddOrganizationToHeadersWhenBuilderIsCalledWithoutOrganizationId () {
63
- var openAI = SimpleOpenAI .builder ()
64
- .apiKey ("apiKey" )
65
- .build ();
66
- assertFalse (openAI .getCleverClient ().getHeaders ().containsValue (openAI .getOrganizationId ()));
67
- }
65
+ @ Test
66
+ void shouldAddOrganizationToHeadersWhenBuilderIsCalledWithOrganizationId () {
67
+ var openAI = SimpleOpenAI .builder ()
68
+ .apiKey ("apiKey" )
69
+ .organizationId ("orgId" )
70
+ .build ();
71
+ assertTrue (openAI .getCleverClient ().getHeaders ().containsValue (openAI .getOrganizationId ()));
72
+ }
68
73
69
- @ Test
70
- void shouldAddOrganizationToHeadersWhenBuilderIsCalledWithOrganizationId () {
71
- var openAI = SimpleOpenAI .builder ()
72
- .apiKey ("apiKey" )
73
- .organizationId ("orgId" )
74
+ @ Test
75
+ @ SuppressWarnings ("unchecked" )
76
+ void shouldNotDuplicateContentTypeHeaderWhenCallingSimpleOpenAI () {
77
+ var chatService = SimpleOpenAI .builder ()
78
+ .apiKey ("apiKey" )
79
+ .httpClient (httpClient )
80
+ .build ()
81
+ .chatCompletions ();
82
+
83
+ // When
84
+ final var NO_OF_REQUESTS = 2 ;
85
+ when (httpClient .sendAsync (any (), any ()))
86
+ .thenReturn (completedFuture (mock (HttpResponse .class )));
87
+
88
+ repeat (NO_OF_REQUESTS , () -> {
89
+ var chatRequest = ChatRequest .builder ()
90
+ .model ("model" )
91
+ .messages (List .of ())
74
92
.build ();
75
- assertTrue ( openAI . getCleverClient (). getHeaders (). containsValue ( openAI . getOrganizationId ()) );
76
- }
93
+ chatService . create ( chatRequest );
94
+ });
77
95
78
- @ Test
79
- @ SuppressWarnings ("unchecked" )
80
- void shouldNotDuplicateContentTypeHeaderWhenCallingSimpleOpenAI () {
81
- var chatService = SimpleOpenAI .builder ()
82
- .apiKey ("apiKey" )
83
- .httpClient (httpClient )
84
- .build ()
85
- .chatCompletions ();
86
-
87
- // When
88
- final var NO_OF_REQUESTS = 2 ;
89
- when (httpClient .sendAsync (any (), any ()))
90
- .thenReturn (completedFuture (mock (HttpResponse .class )));
91
-
92
- repeat (NO_OF_REQUESTS , () -> {
93
- var chatRequest = ChatRequest .builder ()
94
- .model ("model" )
95
- .messages (List .of ())
96
- .build ();
97
- chatService .create (chatRequest );
98
- });
99
-
100
- // Then
101
- ArgumentCaptor <HttpRequest > requestCaptor = ArgumentCaptor .forClass (HttpRequest .class );
102
- verify (httpClient , times (NO_OF_REQUESTS ))
103
- .sendAsync (requestCaptor .capture (), any ());
104
-
105
- var actualRequest = requestCaptor .getAllValues ().get (NO_OF_REQUESTS - 1 );
106
- assertEquals (1 , actualRequest .headers ().allValues ("Content-Type" ).size (),
107
- "Contains Content-Type header exactly once" );
108
- }
96
+ // Then
97
+ ArgumentCaptor <HttpRequest > requestCaptor = ArgumentCaptor .forClass (HttpRequest .class );
98
+ verify (httpClient , times (NO_OF_REQUESTS ))
99
+ .sendAsync (requestCaptor .capture (), any ());
100
+
101
+ var actualRequest = requestCaptor .getAllValues ().get (NO_OF_REQUESTS - 1 );
102
+ assertEquals (1 , actualRequest .headers ().allValues ("Content-Type" ).size (),
103
+ "Contains Content-Type header exactly once" );
109
104
}
110
105
111
- @ Nested
112
- class InitializedTests {
106
+ @ Test
107
+ void shouldInstanceServiceOnlyOnceWhenItIsCalledSeverlaTimes () {
113
108
final int NUMBER_CALLINGS = 3 ;
114
109
final int NUMBER_INVOCATIONS = 1 ;
115
110
116
- SimpleOpenAI openAI ;
117
-
118
- @ BeforeEach
119
- void init () {
120
- openAI = SimpleOpenAI .builder ()
121
- .apiKey ("apiKey" )
122
- .httpClient (httpClient )
123
- .build ();
124
- openAI .setCleverClient (cleverClient );
125
- }
126
-
127
- @ Test
128
- void shouldInstanceAudioServiceOnlyOnceWhenItIsCalledSeveralTimes () {
129
- when (cleverClient .create (any ()))
130
- .thenReturn (ReflectUtil .createProxy (
131
- OpenAI .Audios .class ,
132
- HttpProcessor .builder ().build ()));
133
- repeat (NUMBER_CALLINGS , () -> openAI .audios ());
134
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
135
- }
136
-
137
- @ Test
138
- void shouldInstanceChatCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes () {
139
- when (cleverClient .create (any ()))
140
- .thenReturn (ReflectUtil .createProxy (
141
- OpenAI .ChatCompletions .class ,
142
- HttpProcessor .builder ().build ()));
143
- repeat (NUMBER_CALLINGS , () -> openAI .chatCompletions ());
144
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
145
- }
146
-
147
- @ Test
148
- void shouldInstanceCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes () {
111
+ SimpleOpenAI openAI = SimpleOpenAI .builder ()
112
+ .apiKey ("apiKey" )
113
+ .httpClient (httpClient )
114
+ .build ();
115
+ openAI .setCleverClient (cleverClient );
116
+
117
+ TestData [] data = {
118
+ new TestData (OpenAI .Audios .class , openAI ::audios ),
119
+ new TestData (OpenAI .ChatCompletions .class , openAI ::chatCompletions ),
120
+ new TestData (OpenAI .Completions .class , openAI ::completions ),
121
+ new TestData (OpenAI .Embeddings .class , openAI ::embeddings ),
122
+ new TestData (OpenAI .Files .class , openAI ::files ),
123
+ new TestData (OpenAI .FineTunings .class , openAI ::fineTunings ),
124
+ new TestData (OpenAI .Images .class , openAI ::images ),
125
+ new TestData (OpenAI .Models .class , openAI ::models ),
126
+ new TestData (OpenAI .Moderations .class , openAI ::moderations ),
127
+ new TestData (OpenAI .Assistants .class , openAI ::assistants ),
128
+ new TestData (OpenAI .Threads .class , openAI ::threads )
129
+ };
130
+ for (TestData testData : data ) {
149
131
when (cleverClient .create (any ()))
150
- .thenReturn (ReflectUtil .createProxy (
151
- OpenAI .Completions .class ,
152
- HttpProcessor .builder ().build ()));
153
- repeat (NUMBER_CALLINGS , () -> openAI .completions ());
154
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
155
- }
156
-
157
- @ Test
158
- void shouldInstanceEmbeddingServiceOnlyOnceWhenItIsCalledSeveralTimes () {
159
- when (cleverClient .create (any ()))
160
- .thenReturn (ReflectUtil .createProxy (
161
- OpenAI .Embeddings .class ,
162
- HttpProcessor .builder ().build ()));
163
- repeat (NUMBER_CALLINGS , () -> openAI .embeddings ());
164
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
165
- }
166
-
167
- @ Test
168
- void shouldInstanceFilesServiceOnlyOnceWhenItIsCalledSeveralTimes () {
169
- when (cleverClient .create (any ()))
170
- .thenReturn (ReflectUtil .createProxy (
171
- OpenAI .Files .class ,
172
- HttpProcessor .builder ().build ()));
173
- repeat (NUMBER_CALLINGS , () -> openAI .files ());
174
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
175
- }
176
-
177
- @ Test
178
- void shouldInstanceFineTunningServiceOnlyOnceWhenItIsCalledSeveralTimes () {
179
- when (cleverClient .create (any ()))
180
- .thenReturn (ReflectUtil .createProxy (
181
- OpenAI .FineTunings .class ,
182
- HttpProcessor .builder ().build ()));
183
- repeat (NUMBER_CALLINGS , () -> openAI .fineTunings ());
184
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
132
+ .thenReturn (createProxy (testData .serviceClass ));
133
+ repeat (NUMBER_CALLINGS , testData .calling );
134
+ verify (cleverClient , times (NUMBER_INVOCATIONS )).create (testData .serviceClass );
185
135
}
136
+ }
186
137
187
- @ Test
188
- void shouldInstanceImageServiceOnlyOnceWhenItIsCalledSeveralTimes () {
189
- when (cleverClient .create (any ()))
190
- .thenReturn (ReflectUtil .createProxy (
191
- OpenAI .Images .class ,
192
- HttpProcessor .builder ().build ()));
193
- repeat (NUMBER_CALLINGS , () -> openAI .images ());
194
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
195
- }
138
+ private static void repeat (int times , Runnable action ) {
139
+ for (var i = 0 ; i < times ; i ++)
140
+ action .run ();
141
+ }
196
142
197
- @ Test
198
- void shouldInstanceModelsServiceOnlyOnceWhenItIsCalledSeveralTimes () {
199
- when (cleverClient .create (any ()))
200
- .thenReturn (ReflectUtil .createProxy (
201
- OpenAI .Models .class ,
202
- HttpProcessor .builder ().build ()));
203
- repeat (NUMBER_CALLINGS , () -> openAI .models ());
204
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
205
- }
143
+ static class TestData {
144
+ private Class <?> serviceClass ;
145
+ private Runnable calling ;
206
146
207
- @ Test
208
- void shouldInstanceModerationServiceOnlyOnceWhenItIsCalledSeveralTimes () {
209
- when (cleverClient .create (any ()))
210
- .thenReturn (ReflectUtil .createProxy (
211
- OpenAI .Moderations .class ,
212
- HttpProcessor .builder ().build ()));
213
- repeat (NUMBER_CALLINGS , () -> openAI .moderations ());
214
- verify (cleverClient , times (NUMBER_INVOCATIONS )).create (any ());
147
+ public TestData (Class <?> serviceClass , Runnable calling ) {
148
+ this .serviceClass = serviceClass ;
149
+ this .calling = calling ;
215
150
}
216
151
}
217
152
218
- private static void repeat (int times , Runnable action ) {
219
- for (var i = 0 ; i < times ; i ++)
220
- action .run ();
153
+ @ SuppressWarnings ("unchecked" )
154
+ private static <T > T createProxy (Class <T > interfaceClass ) {
155
+ return (T ) Proxy .newProxyInstance (
156
+ interfaceClass .getClassLoader (),
157
+ new Class <?>[] { interfaceClass },
158
+ new InvocationHandler () {
159
+ @ Override
160
+ public Object invoke (Object proxy , Method method , Object [] args ) throws Throwable {
161
+ throw new UnsupportedOperationException ("Unimplemented method 'invoke'" );
162
+ }
163
+ });
221
164
}
222
165
}
0 commit comments