Skip to content

Commit f5d8775

Browse files
committed
ci: introduce more tests for scenario endpoint
1 parent bdcfa3a commit f5d8775

1 file changed

Lines changed: 286 additions & 9 deletions

File tree

simulator-spring-boot/src/test/java/org/citrusframework/simulator/scenario/ScenarioEndpointTest.java

Lines changed: 286 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,329 @@
1616

1717
package org.citrusframework.simulator.scenario;
1818

19+
import org.citrusframework.context.TestContext;
20+
import org.citrusframework.exceptions.CitrusRuntimeException;
1921
import org.citrusframework.message.Message;
22+
import org.citrusframework.simulator.endpoint.EndpointMessageHandler;
2023
import org.citrusframework.simulator.endpoint.SimulationFailedUnexpectedlyException;
2124
import org.citrusframework.simulator.exception.SimulatorException;
25+
import org.citrusframework.spi.ReferenceResolver;
2226
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Disabled;
2328
import org.junit.jupiter.api.Nested;
2429
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.extension.ExtendWith;
31+
import org.mockito.ArgumentCaptor;
32+
import org.mockito.Mock;
33+
import org.mockito.junit.jupiter.MockitoExtension;
2534

35+
import java.time.Duration;
2636
import java.util.concurrent.CompletableFuture;
37+
import java.util.concurrent.CountDownLatch;
38+
import java.util.concurrent.ExecutionException;
39+
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.ThreadLocalRandom;
41+
import java.util.concurrent.TimeoutException;
2742

43+
import static java.util.Objects.nonNull;
44+
import static java.util.concurrent.Executors.newFixedThreadPool;
45+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
46+
import static java.util.concurrent.locks.LockSupport.parkNanos;
2847
import static org.assertj.core.api.Assertions.assertThat;
2948
import static org.assertj.core.api.Assertions.assertThatThrownBy;
49+
import static org.mockito.ArgumentCaptor.captor;
50+
import static org.mockito.ArgumentMatchers.any;
51+
import static org.mockito.Mockito.doReturn;
3052
import static org.mockito.Mockito.mock;
53+
import static org.mockito.Mockito.verify;
54+
import static org.mockito.Mockito.verifyNoInteractions;
55+
import static org.mockito.Mockito.verifyNoMoreInteractions;
3156

57+
@ExtendWith({MockitoExtension.class})
3258
class ScenarioEndpointTest {
3359

60+
@Mock
61+
private EndpointMessageHandler endpointMessageHandlerMock;
62+
63+
@Mock
64+
private ScenarioEndpointConfiguration scenarioEndpointConfigurationMock;
65+
3466
private ScenarioEndpoint fixture;
3567

3668
@BeforeEach
3769
void beforeEachSetup() {
38-
fixture = new ScenarioEndpoint(null);
70+
fixture = new ScenarioEndpoint(scenarioEndpointConfigurationMock);
71+
}
72+
73+
@Nested
74+
class CreateProducer {
75+
76+
@Test
77+
void shouldReturnSelf() {
78+
assertThat(fixture.createProducer()).isSameAs(fixture);
79+
}
80+
}
81+
82+
@Nested
83+
class CreateConsumer {
84+
85+
@Test
86+
void shouldReturnSelf() {
87+
assertThat(fixture.createConsumer()).isSameAs(fixture);
88+
}
3989
}
4090

4191
@Nested
4292
class Fail {
4393

4494
@Test
45-
void throwsExceptionWhenNoResponseFuturePresent() {
46-
assertThatThrownBy(() -> fixture.fail(null))
95+
@Disabled
96+
void shouldThrowException_ifNoResponseFutureIsPresent() {
97+
var testContextMock = mock(TestContext.class);
98+
99+
assertThatThrownBy(() -> fixture.fail(new CitrusRuntimeException()))
47100
.isInstanceOf(SimulatorException.class)
48-
.hasMessage("Failed to process scenario response message - missing response consumer!");
101+
.hasMessage("Failed to receive scenario inbound message");
102+
103+
verifyNoInteractions(testContextMock);
49104
}
50105

51106
@Test
52-
void completesResponseFutureIfOneIsPresent() {
53-
CompletableFuture<Message> responseFuture = new CompletableFuture<>();
107+
void shouldPollNextMessageFromChannel_ifNoneHasBeenReceived() {
108+
CompletableFuture<Message> responseFuture = mock();
54109
fixture.add(mock(Message.class), responseFuture);
55110

56111
var cause = mock(Throwable.class);
57112
fixture.fail(cause);
58113

59-
assertThat(responseFuture)
60-
.isCompleted();
61-
assertThat(responseFuture.join())
114+
ArgumentCaptor<SimulationFailedUnexpectedlyException> responseMessageArgumentCaptor = captor();
115+
verify(responseFuture).complete(responseMessageArgumentCaptor.capture());
116+
117+
assertThat(responseMessageArgumentCaptor.getValue())
118+
.isNotNull()
62119
.isInstanceOf(SimulationFailedUnexpectedlyException.class)
63120
.extracting(Message::getPayload)
64121
.isEqualTo(cause);
65122
}
123+
124+
@Test
125+
void shouldCompleteSingleResponseFuture_ifOneIsPresent() {
126+
CompletableFuture<Message> responseFuture = mock();
127+
fixture.add(mock(Message.class), responseFuture);
128+
129+
var cause = mock(Throwable.class);
130+
fixture.fail(cause);
131+
132+
ArgumentCaptor<SimulationFailedUnexpectedlyException> responseMessageArgumentCaptor = captor();
133+
verify(responseFuture).complete(responseMessageArgumentCaptor.capture());
134+
135+
assertThat(responseMessageArgumentCaptor.getValue())
136+
.isNotNull()
137+
.isInstanceOf(SimulationFailedUnexpectedlyException.class)
138+
.extracting(Message::getPayload)
139+
.isEqualTo(cause);
140+
}
141+
142+
@Test
143+
@Disabled
144+
void shouldResolveFuturesCorrectlyIn_FIFO_Order() {
145+
var params = addAndReceiveTwoMessagesInOrder();
146+
147+
var cause1 = mock(Throwable.class);
148+
fixture.fail(cause1);
149+
150+
verify(params.responseFuture1()).complete(any(SimulationFailedUnexpectedlyException.class));
151+
verifyNoInteractions(params.responseFuture2());
152+
153+
var cause2 = mock(Throwable.class);
154+
fixture.fail(cause2);
155+
156+
verify(params.responseFuture2()).complete(any(SimulationFailedUnexpectedlyException.class));
157+
verifyNoMoreInteractions(params.responseFuture1(), params.responseFuture2());
158+
}
159+
160+
@Test
161+
void shouldResolveFuturesCorrectlyIn_FILO_Order() {
162+
var params = addAndReceiveTwoMessagesInOrder();
163+
164+
var cause1 = mock(Throwable.class);
165+
fixture.fail(cause1);
166+
167+
verify(params.responseFuture2()).complete(any(SimulationFailedUnexpectedlyException.class));
168+
verifyNoInteractions(params.responseFuture1());
169+
170+
var cause2 = mock(Throwable.class);
171+
fixture.fail(cause2);
172+
173+
verify(params.responseFuture1()).complete(any(SimulationFailedUnexpectedlyException.class));
174+
verifyNoMoreInteractions(params.responseFuture1(), params.responseFuture2());
175+
}
176+
}
177+
178+
@Nested
179+
class Send {
180+
181+
@Test
182+
void shouldPollNextMessageFromChannel_ifNoneHasBeenReceived() {
183+
var message = mock(Message.class);
184+
CompletableFuture<Message> responseFuture = mock();
185+
186+
fixture.add(message, responseFuture);
187+
188+
fixture.send(message, mockTestContext());
189+
190+
verify(responseFuture).complete(message);
191+
}
192+
193+
@Test
194+
@Disabled
195+
void shouldResolveFuturesCorrectlyIn_FIFO_Order() {
196+
var params = addAndReceiveTwoMessagesInOrder();
197+
198+
fixture.send(params.message1(), params.testContext1());
199+
200+
verify(params.responseFuture1()).complete(params.message1());
201+
verifyNoInteractions(params.responseFuture2());
202+
203+
fixture.send(params.message2(), params.testContext2());
204+
205+
verify(params.responseFuture2()).complete(params.message2());
206+
verifyNoMoreInteractions(params.responseFuture1(), params.responseFuture2());
207+
}
208+
209+
@Test
210+
void shouldResolveFuturesCorrectlyIn_FILO_Order() {
211+
var params = addAndReceiveTwoMessagesInOrder();
212+
213+
fixture.send(params.message2(), params.testContext2());
214+
215+
verify(params.responseFuture2()).complete(params.message2());
216+
verifyNoInteractions(params.responseFuture1());
217+
218+
fixture.send(params.message1(), params.testContext1());
219+
220+
verify(params.responseFuture1()).complete(params.message1());
221+
verifyNoMoreInteractions(params.responseFuture1(), params.responseFuture2());
222+
}
223+
224+
@Test
225+
void shouldSkipUnresolvedFutures() {
226+
var params = addAndReceiveTwoMessagesInOrder();
227+
228+
fixture.send(params.message2(), params.testContext2());
229+
230+
verify(params.responseFuture2()).complete(params.message2());
231+
verifyNoMoreInteractions(params.responseFuture2());
232+
verifyNoInteractions(params.responseFuture1());
233+
}
234+
235+
@Test
236+
void shouldBeThreadSafe() throws InterruptedException {
237+
var threadCount = 10;
238+
ExecutorService executorService = null;
239+
240+
try {
241+
executorService = newFixedThreadPool(threadCount);
242+
spawnAndCompleteScenarioExecutionsForThreads(threadCount, executorService);
243+
} finally {
244+
if (nonNull(executorService)) {
245+
executorService.shutdownNow();
246+
}
247+
}
248+
}
249+
250+
@SuppressWarnings({"unchecked"})
251+
private void spawnAndCompleteScenarioExecutionsForThreads(int threadCount, ExecutorService executorService) throws InterruptedException {
252+
var latch = new CountDownLatch(threadCount);
253+
254+
var testContexts = new TestContext[threadCount];
255+
var responseFutures = new CompletableFuture<?>[threadCount];
256+
var requests = new Message[threadCount];
257+
var responses = new Message[threadCount];
258+
259+
for (int i = 0; i < threadCount; i++) {
260+
testContexts[i] = mockTestContext();
261+
responseFutures[i] = new CompletableFuture<Message>();
262+
requests[i] = mock();
263+
responses[i] = mock();
264+
265+
fixture.add(requests[i], (CompletableFuture<Message>) responseFutures[i]);
266+
}
267+
268+
for (int i = 0; i < threadCount; i++) {
269+
final var index = i;
270+
executorService.submit(() -> {
271+
try {
272+
executeScenario(testContexts[index], requests[index], responses[index], (CompletableFuture<Message>) responseFutures[index]);
273+
} catch (Exception e) {
274+
throw new CitrusRuntimeException(e);
275+
} finally {
276+
latch.countDown();
277+
}
278+
});
279+
}
280+
281+
if (!latch.await(300, MILLISECONDS)) {
282+
throw new AssertionError("Not all tasks completed in time!");
283+
}
284+
}
285+
286+
private void executeScenario(TestContext testContext, Message request, Message response, CompletableFuture<Message> responseFuture) throws InterruptedException, ExecutionException, TimeoutException {
287+
var received = fixture.receive(testContext);
288+
assertThat(received)
289+
.isNotNull()
290+
.isEqualTo(request);
291+
292+
parkNanos(Duration.ofMillis(ThreadLocalRandom.current().nextInt(10, 100)).toNanos());
293+
294+
fixture.send(response, testContext);
295+
296+
var result = responseFuture.get(200, MILLISECONDS);
297+
assertThat(result)
298+
.isNotNull()
299+
.isEqualTo(response);
300+
}
301+
}
302+
303+
private ConcurrentTestExecutionParams addAndReceiveTwoMessagesInOrder() {
304+
var testContext1 = mockTestContext();
305+
var message1 = mock(Message.class);
306+
CompletableFuture<Message> responseFuture1 = mock();
307+
308+
fixture.add(message1, responseFuture1);
309+
310+
var testContext2 = mockTestContext();
311+
var message2 = mock(Message.class);
312+
CompletableFuture<Message> responseFuture2 = mock();
313+
314+
fixture.add(message2, responseFuture2);
315+
316+
var receiveMessage1 = fixture.receive(testContext1);
317+
assertThat(receiveMessage1)
318+
.isNotNull()
319+
.isEqualTo(message1);
320+
321+
var receiveMessage2 = fixture.receive(testContext2);
322+
assertThat(receiveMessage2)
323+
.isNotNull()
324+
.isEqualTo(message2);
325+
326+
return new ConcurrentTestExecutionParams(testContext1, message1, responseFuture1, testContext2, message2, responseFuture2);
327+
}
328+
329+
private TestContext mockTestContext() {
330+
var testContextMock = mock(TestContext.class);
331+
var referenceResolverMock = mock(ReferenceResolver.class);
332+
333+
doReturn(referenceResolverMock).when(testContextMock).getReferenceResolver();
334+
doReturn(endpointMessageHandlerMock).when(referenceResolverMock).resolve(EndpointMessageHandler.class);
335+
336+
return testContextMock;
337+
}
338+
339+
private record ConcurrentTestExecutionParams(TestContext testContext1, Message message1,
340+
CompletableFuture<Message> responseFuture1,
341+
TestContext testContext2, Message message2,
342+
CompletableFuture<Message> responseFuture2) {
66343
}
67344
}

0 commit comments

Comments
 (0)