Skip to content

Commit 9476cb9

Browse files
author
Adam Soos
committed
RCB-597: add tests
1 parent 5d12f09 commit 9476cb9

2 files changed

Lines changed: 235 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* Copyright 2023 Basis Technology Corp.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.basistech.rosette.api;
17+
18+
import com.basistech.rosette.api.common.AbstractRosetteAPI;
19+
import com.basistech.rosette.apimodel.DocumentRequest;
20+
import com.basistech.rosette.apimodel.EntitiesOptions;
21+
import com.basistech.rosette.apimodel.EntitiesResponse;
22+
import com.basistech.rosette.apimodel.ErrorResponse;
23+
import com.basistech.rosette.apimodel.LanguageOptions;
24+
import com.basistech.rosette.apimodel.LanguageResponse;
25+
import com.basistech.rosette.apimodel.Response;
26+
import org.apache.http.impl.client.CloseableHttpClient;
27+
import org.apache.http.impl.client.HttpClients;
28+
import org.junit.jupiter.api.AfterEach;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.extension.ExtendWith;
32+
import org.mockserver.client.MockServerClient;
33+
import org.mockserver.junit.jupiter.MockServerExtension;
34+
import org.mockserver.matchers.Times;
35+
import org.mockserver.model.HttpRequest;
36+
import org.mockserver.model.HttpResponse;
37+
38+
import java.io.IOException;
39+
import java.util.ArrayList;
40+
import java.util.Date;
41+
import java.util.List;
42+
import java.util.concurrent.ExecutionException;
43+
import java.util.concurrent.Future;
44+
import java.util.concurrent.TimeUnit;
45+
46+
import static org.junit.jupiter.api.Assertions.assertEquals;
47+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
48+
import static org.junit.jupiter.api.Assertions.assertNull;
49+
import static org.junit.jupiter.api.Assertions.assertTrue;
50+
51+
@ExtendWith(MockServerExtension.class)
52+
class RosetteRequestTest {
53+
private MockServerClient mockServer;
54+
private HttpRosetteAPI api;
55+
56+
@BeforeEach
57+
void setup(MockServerClient mockServer) {
58+
this.mockServer = mockServer;
59+
}
60+
61+
private void setupResponse(String requestPath, String responseString, int statusCode, int delayMillis, int requestTimes) {
62+
this.mockServer.when(HttpRequest.request().withPath(requestPath), Times.exactly(requestTimes))
63+
.respond(HttpResponse.response()
64+
.withHeader("Content-Type", "application/json")
65+
.withHeader("X-RosetteAPI-Concurrency", "5")
66+
.withStatusCode(statusCode)
67+
.withBody(responseString)
68+
.withDelay(TimeUnit.MILLISECONDS, delayMillis));
69+
}
70+
71+
72+
@Test
73+
void successfulRequest() throws ExecutionException, InterruptedException {
74+
//Api client setup
75+
this.api = new HttpRosetteAPI.Builder().url(String.format("http://localhost:%d/rest/v1", mockServer.getPort())).build();
76+
77+
//response setup
78+
String entitiesResponse = "{\"entities\" : [ { \"type\" : \"ORGANIZATION\", \"mention\" : \"Securities and Exchange Commission\", \"normalized\" : \"U.S. Securities and Exchange Commission\", \"count\" : 1, \"mentionOffsets\" : [ { \"startOffset\" : 4, \"endOffset\" : 38 } ], \"entityId\" : \"Q953944\", \"confidence\" : 0.39934742, \"linkingConfidence\" : 0.67404154 } ] }";
79+
setupResponse("/rest/v1/entities", entitiesResponse, 200, 0, 1);
80+
81+
//request setup
82+
String entitiesTextData = "The Securities and Exchange Commission today announced the leadership of the agency’s trial unit.";
83+
DocumentRequest<EntitiesOptions> entitiesRequestData = DocumentRequest.<EntitiesOptions>builder()
84+
.content(entitiesTextData)
85+
.build();
86+
RosetteRequest entitiesRequest = this.api.createRosetteRequest(AbstractRosetteAPI.ENTITIES_SERVICE_PATH, entitiesRequestData, EntitiesResponse.class);
87+
88+
//testing the request
89+
Future<Response> response = this.api.submitRequest(entitiesRequest);
90+
assertInstanceOf(EntitiesResponse.class, response.get());
91+
assertEquals(response.get(), entitiesRequest.getResponse());
92+
}
93+
94+
95+
@Test
96+
void errorResponse() throws ExecutionException, InterruptedException {
97+
//Api client setup
98+
this.api = new HttpRosetteAPI.Builder().url(String.format("http://localhost:%d/rest/v1", mockServer.getPort())).build();
99+
100+
//response setup
101+
String entitiesResponse = "{ \"code\" : \"badRequestFormat\", \"message\" : \"no content provided; must be one of an attachment, an inline \\\"content\\\" field, or an external \\\"contentUri\\\"\" }";
102+
setupResponse("/rest/v1/entities", entitiesResponse, 400, 0, 1);
103+
104+
//request setup
105+
DocumentRequest<EntitiesOptions> entitiesRequestData = DocumentRequest.<EntitiesOptions>builder()
106+
.build();
107+
RosetteRequest entitiesRequest = this.api.createRosetteRequest(AbstractRosetteAPI.ENTITIES_SERVICE_PATH, entitiesRequestData, EntitiesResponse.class);
108+
109+
//testing the request
110+
Future<Response> response = this.api.submitRequest(entitiesRequest);
111+
assertInstanceOf(ErrorResponse.class, response.get());
112+
assertEquals(response.get(), entitiesRequest.getResponse());
113+
}
114+
115+
@Test
116+
void testTiming() throws ExecutionException, InterruptedException {
117+
int delay = 100;
118+
//api setup
119+
this.api = new HttpRosetteAPI.Builder().url(String.format("http://localhost:%d/rest/v1", mockServer.getPort()))
120+
.connectionConcurrency(1).build();
121+
122+
//responses setup
123+
int entitiesRespCount = 10;
124+
int languageRespCount = 4;
125+
assertEquals(0, entitiesRespCount % 2);
126+
assertEquals(0, entitiesRespCount % 2);
127+
String entitiesResponse = "{\"entities\" : [ { \"type\" : \"ORGANIZATION\", \"mention\" : \"Securities and Exchange Commission\", \"normalized\" : \"U.S. Securities and Exchange Commission\", \"count\" : 1, \"mentionOffsets\" : [ { \"startOffset\" : 4, \"endOffset\" : 38 } ], \"entityId\" : \"Q953944\", \"confidence\" : 0.39934742, \"linkingConfidence\" : 0.67404154 } ] }";
128+
setupResponse("/rest/v1/entities", entitiesResponse, 200, delay, entitiesRespCount);
129+
String languageResponse = " {\"code\" : \"badRequestFormat\", \"message\" : \"no content provided; must be one of an attachment, an inline \\\"content\\\" field, or an external \\\"contentUri\\\"\" }";
130+
setupResponse("/rest/v1/language", languageResponse, 400, delay, languageRespCount);
131+
132+
//requests setup
133+
String entitiesTextData = "The Securities and Exchange Commission today announced the leadership of the agency’s trial unit.";
134+
DocumentRequest<EntitiesOptions> entitiesRequestData = DocumentRequest.<EntitiesOptions>builder()
135+
.content(entitiesTextData)
136+
.build();
137+
DocumentRequest<LanguageOptions> languageRequestData = DocumentRequest.<LanguageOptions>builder().build();
138+
List<RosetteRequest> requests = new ArrayList<>();
139+
for (int i = 0; i < entitiesRespCount / 2; i++) {
140+
requests.add(this.api.createRosetteRequest(AbstractRosetteAPI.ENTITIES_SERVICE_PATH, entitiesRequestData, EntitiesResponse.class));
141+
}
142+
for (int i = 0; i < languageRespCount / 2; i++) {
143+
requests.add(this.api.createRosetteRequest(AbstractRosetteAPI.LANGUAGE_SERVICE_PATH, languageRequestData, LanguageResponse.class));
144+
}
145+
146+
//run requests
147+
Date d1 = new Date();
148+
List<Future<Response>> responses = this.api.submitRequests(requests);
149+
for (int i = 0; i < responses.size(); i++) {
150+
responses.get(i).get();
151+
}
152+
Date d2 = new Date();
153+
154+
assertTrue(d2.getTime() - d1.getTime() > delay * requests.size()); // at least as long as the delay in the request
155+
156+
//run requests concurrency
157+
int concurrency = 3;
158+
this.api = new HttpRosetteAPI.Builder().url(String.format("http://localhost:%d/rest/v1", mockServer.getPort()))
159+
.connectionConcurrency(3).build();
160+
161+
requests = new ArrayList<>();
162+
for (int i = 0; i < entitiesRespCount / 2; i++) {
163+
requests.add(this.api.createRosetteRequest(AbstractRosetteAPI.ENTITIES_SERVICE_PATH, entitiesRequestData, EntitiesResponse.class));
164+
}
165+
for (int i = 0; i < entitiesRespCount / 2; i++) {
166+
requests.add(this.api.createRosetteRequest(AbstractRosetteAPI.LANGUAGE_SERVICE_PATH, languageRequestData, LanguageResponse.class));
167+
}
168+
169+
170+
d1 = new Date();
171+
responses = this.api.submitRequests(requests);
172+
for (int i = 0; i < responses.size(); i++) {
173+
responses.get(i).get();
174+
}
175+
d2 = new Date();
176+
177+
assertTrue(d2.getTime() - d1.getTime() < delay * requests.size()); // less than serial requests
178+
assertTrue(d2.getTime() - d1.getTime() > requests.size() / concurrency * delay); // running faster than this would suggest it exceeds the maximum concurrency
179+
}
180+
181+
private RosetteRequest setupShutdownTest(int shutdownWaitSeconds, int responseDelayMillis, CloseableHttpClient client) {
182+
this.api = new HttpRosetteAPI.Builder()
183+
.url(String.format("http://localhost:%d/rest/v1", mockServer.getPort()))
184+
.shutdownWait(shutdownWaitSeconds)
185+
.httpClient(client)
186+
.build();
187+
188+
//response setup
189+
String entitiesResponse = "{\"entities\" : [ { \"type\" : \"ORGANIZATION\", \"mention\" : \"Securities and Exchange Commission\", \"normalized\" : \"U.S. Securities and Exchange Commission\", \"count\" : 1, \"mentionOffsets\" : [ { \"startOffset\" : 4, \"endOffset\" : 38 } ], \"entityId\" : \"Q953944\", \"confidence\" : 0.39934742, \"linkingConfidence\" : 0.67404154 } ] }";
190+
setupResponse("/rest/v1/entities", entitiesResponse, 200, responseDelayMillis, 1);
191+
192+
//request setup
193+
String entitiesTextData = "The Securities and Exchange Commission today announced the leadership of the agency’s trial unit.";
194+
DocumentRequest<EntitiesOptions> entitiesRequestData = DocumentRequest.<EntitiesOptions>builder()
195+
.content(entitiesTextData)
196+
.build();
197+
return this.api.createRosetteRequest(AbstractRosetteAPI.ENTITIES_SERVICE_PATH, entitiesRequestData, EntitiesResponse.class);
198+
}
199+
200+
@Test
201+
void successfulShutdown() throws IOException {
202+
/* creating http client to avoid closing the httpClient of HttpRosetteApi during close()
203+
* which automatically closes the pending threads. This way the shutdown behaviour can be tested.*/
204+
CloseableHttpClient httpClient = HttpClients.createDefault();
205+
int shutdownWait = 3;
206+
RosetteRequest request = setupShutdownTest(shutdownWait, 1000, httpClient);
207+
this.api.submitRequest(request);
208+
this.api.close();
209+
httpClient.close();
210+
211+
assertInstanceOf(EntitiesResponse.class, request.getResponse()); //got response successfully
212+
}
213+
214+
@Test
215+
void timeoutShutdown() throws IOException {
216+
/* creating http client to avoid closing the httpClient of HttpRosetteApi during close()
217+
* which automatically closes the pending threads. This way the shutdown behaviour can be tested.*/
218+
CloseableHttpClient httpClient = HttpClients.createDefault();
219+
int shutdownWait = 3;
220+
RosetteRequest request = setupShutdownTest(shutdownWait, 5000, httpClient);
221+
this.api.submitRequest(request);
222+
this.api.close();
223+
224+
httpClient.close();
225+
assertNull(request.getResponse()); //didn't get a response, it was forcefully shutdown
226+
}
227+
228+
229+
@AfterEach
230+
void after() throws IOException {
231+
this.api.close();
232+
}
233+
234+
}

examples/src/main/java/com/basistech/rosette/examples/ConcurrencyExample.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private void run() throws IOException, ExecutionException, InterruptedException
8989
LanguageResponse.class)
9090
);
9191
// Setting up morphology request
92+
// No content is given to this request and it will return an error response
9293
threads.add(
9394
rosetteApi.createRosetteRequest(MORPHOLOGY_SERVICE_PATH + "/" + MorphologicalFeature.COMPLETE,
9495
DocumentRequest.<MorphologyOptions>builder().build(),

0 commit comments

Comments
 (0)