Skip to content

Commit 4c78f0d

Browse files
authored
Merge pull request #46 from sashirestela/44-refactoring-some-code
Refactoring some code
2 parents f8e8923 + 374db0a commit 4c78f0d

File tree

3 files changed

+117
-176
lines changed

3 files changed

+117
-176
lines changed

src/demo/java/io/github/sashirestela/openai/demo/FileServiceDemo.java

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public FileResponse createFileResponse() {
2020
return futureFile.join();
2121
}
2222

23+
@SuppressWarnings("deprecation")
2324
public void waitUntilFileIsProcessed(String fileId) {
2425
FileResponse fileResponse = null;
2526
do {

src/main/java/io/github/sashirestela/openai/SimpleOpenAI.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import lombok.Builder;
1111
import lombok.Getter;
1212
import lombok.NonNull;
13+
import lombok.Setter;
1314

1415
/**
1516
* The factory that generates implementations of the {@link OpenAI OpenAI}
@@ -29,7 +30,7 @@ public class SimpleOpenAI {
2930
private final String organizationId;
3031
private final String baseUrl;
3132
private final HttpClient httpClient;
32-
33+
@Setter
3334
private CleverClient cleverClient;
3435

3536
@Getter(AccessLevel.NONE)
@@ -97,10 +98,6 @@ public SimpleOpenAI(@NonNull String apiKey, String organizationId, String baseUr
9798
.build();
9899
}
99100

100-
public void setCleverClient(CleverClient cleverClient) {
101-
this.cleverClient = cleverClient;
102-
}
103-
104101
/**
105102
* Generates an implementation of the Audios interface to handle requests.
106103
*

src/test/java/io/github/sashirestela/openai/SimpleOpenAITest.java

+114-171
Original file line numberDiff line numberDiff line change
@@ -12,211 +12,154 @@
1212
import static org.mockito.Mockito.verify;
1313
import static org.mockito.Mockito.when;
1414

15+
import java.lang.reflect.InvocationHandler;
16+
import java.lang.reflect.Method;
17+
import java.lang.reflect.Proxy;
1518
import java.net.http.HttpClient;
1619
import java.net.http.HttpRequest;
1720
import java.net.http.HttpResponse;
1821
import java.util.List;
1922

20-
import org.junit.jupiter.api.BeforeEach;
21-
import org.junit.jupiter.api.Nested;
2223
import org.junit.jupiter.api.Test;
2324
import org.mockito.ArgumentCaptor;
2425

2526
import io.github.sashirestela.cleverclient.CleverClient;
26-
import io.github.sashirestela.cleverclient.http.HttpProcessor;
27-
import io.github.sashirestela.cleverclient.util.ReflectUtil;
2827
import io.github.sashirestela.openai.domain.chat.ChatRequest;
2928

3029
class SimpleOpenAITest {
3130

3231
HttpClient httpClient = mock(HttpClient.class);
3332
CleverClient cleverClient = mock(CleverClient.class);
3433

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+
}
3743

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+
}
4756

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+
}
6064

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+
}
6873

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())
7492
.build();
75-
assertTrue(openAI.getCleverClient().getHeaders().containsValue(openAI.getOrganizationId()));
76-
}
93+
chatService.create(chatRequest);
94+
});
7795

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");
109104
}
110105

111-
@Nested
112-
class InitializedTests {
106+
@Test
107+
void shouldInstanceServiceOnlyOnceWhenItIsCalledSeverlaTimes() {
113108
final int NUMBER_CALLINGS = 3;
114109
final int NUMBER_INVOCATIONS = 1;
115110

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) {
149131
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);
185135
}
136+
}
186137

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+
}
196142

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;
206146

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;
215150
}
216151
}
217152

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+
});
221164
}
222165
}

0 commit comments

Comments
 (0)