mirror of https://github.com/OpenVidu/openvidu.git
WebhookIntegrationTest
parent
22c7e08448
commit
e60ac50d26
|
@ -370,6 +370,24 @@
|
||||||
<version>${version.hamcrest}</version>
|
<version>${version.hamcrest}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.openvidu</groupId>
|
||||||
|
<artifactId>openvidu-test-browsers</artifactId>
|
||||||
|
<version>${version.openvidu.test.browsers}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>net.bytebuddy</groupId>
|
||||||
|
<artifactId>byte-buddy</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.powermock</groupId>
|
||||||
|
<artifactId>powermock-api-mockito2</artifactId>
|
||||||
|
<version>${version.powermock}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test dependencies -->
|
<!-- Test dependencies -->
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
package io.openvidu.server.test.integration;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.refEq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.apache.http.StatusLine;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
|
import org.powermock.reflect.Whitebox;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import io.openvidu.client.internal.ProtocolElements;
|
||||||
|
import io.openvidu.java.client.ConnectionProperties;
|
||||||
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
|
import io.openvidu.server.core.Participant;
|
||||||
|
import io.openvidu.server.core.Session;
|
||||||
|
import io.openvidu.server.core.SessionEventsHandler;
|
||||||
|
import io.openvidu.server.core.Token;
|
||||||
|
import io.openvidu.server.kurento.core.KurentoSessionManager;
|
||||||
|
import io.openvidu.server.rest.SessionRestController;
|
||||||
|
import io.openvidu.server.rpc.RpcNotificationService;
|
||||||
|
import io.openvidu.server.test.integration.config.IntegrationTestConfiguration;
|
||||||
|
import io.openvidu.server.utils.GeoLocation;
|
||||||
|
import io.openvidu.server.webhook.CDRLoggerWebhook;
|
||||||
|
import io.openvidu.server.webhook.HttpWebhookSender;
|
||||||
|
import io.openvidu.test.browsers.utils.webhook.CustomWebhook;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pablo Fuente (pablofuenteperez@gmail.com)
|
||||||
|
*/
|
||||||
|
@SpringBootTest(properties = { "OPENVIDU_WEBHOOK=true", "OPENVIDU_WEBHOOK_ENDPOINT=http://localhost:7777/webhook",
|
||||||
|
"OPENVIDU_WEBHOOK_HEADERS=[]",
|
||||||
|
"OPENVIDU_WEBHOOK_EVENTS=[\"sessionCreated\",\"participantJoined\",\"participantLeft\",\"signalSent\"]" })
|
||||||
|
@TestPropertySource(locations = "classpath:integration-test.properties")
|
||||||
|
@ContextConfiguration(classes = { IntegrationTestConfiguration.class })
|
||||||
|
@WebAppConfiguration
|
||||||
|
public class WebhookIntegrationTest {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WebhookIntegrationTest.class);
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private CallDetailRecord cdr;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private SessionEventsHandler sessionEventsHandler;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
protected RpcNotificationService rpcNotificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected SessionRestController sessionRestController;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected KurentoSessionManager kurentoSessionManager;
|
||||||
|
|
||||||
|
private HttpWebhookSender webhook;
|
||||||
|
|
||||||
|
private void mockWebhookHttpClient(int millisecondsDelayOnResponse) throws ClientProtocolException, IOException {
|
||||||
|
CDRLoggerWebhook cdrLoggerWebhook = (CDRLoggerWebhook) cdr.getLoggers().stream()
|
||||||
|
.filter(logger -> logger instanceof CDRLoggerWebhook).findFirst().get();
|
||||||
|
this.webhook = Whitebox.getInternalState(cdrLoggerWebhook, "webhookSender");
|
||||||
|
|
||||||
|
CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
|
||||||
|
StatusLine statusLine = mock(StatusLine.class);
|
||||||
|
when(statusLine.getStatusCode()).thenReturn(200);
|
||||||
|
when(httpResponse.getStatusLine()).thenReturn(statusLine);
|
||||||
|
|
||||||
|
this.setHttpClientDelay(millisecondsDelayOnResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHttpClientDelay(int millisecondsDelayOnResponse) throws ClientProtocolException, IOException {
|
||||||
|
HttpClient httpClient = PowerMockito.spy((HttpClient) Whitebox.getInternalState(webhook, "httpClient"));
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
Thread.sleep(millisecondsDelayOnResponse);
|
||||||
|
return invocationOnMock.callRealMethod();
|
||||||
|
}).when(httpClient).execute(Mockito.any(HttpUriRequest.class));
|
||||||
|
Whitebox.setInternalState(webhook, "httpClient", httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Webhook event and RPC event should not interfere with each other")
|
||||||
|
void webhookEventAndRpcEventShouldNotInterfereWithEachOtherTest() throws Exception {
|
||||||
|
|
||||||
|
log.info("Webhook event and RPC event should not interfere with each other");
|
||||||
|
|
||||||
|
this.mockWebhookHttpClient(500);
|
||||||
|
|
||||||
|
CountDownLatch initLatch = new CountDownLatch(1);
|
||||||
|
CustomWebhook.main(new String[0], initLatch);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (!initLatch.await(30, TimeUnit.SECONDS)) {
|
||||||
|
Assert.fail("Timeout waiting for webhook springboot app to start");
|
||||||
|
CustomWebhook.shutDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String sessionId = "WEBHOOK_TEST_SESSION";
|
||||||
|
|
||||||
|
this.sessionRestController.initializeSession(Map.of("customSessionId", sessionId));
|
||||||
|
|
||||||
|
// Webhook event "sessionCreated" is delayed 500 ms
|
||||||
|
// Expected TimeoutException
|
||||||
|
assertThrows(TimeoutException.class, () -> {
|
||||||
|
CustomWebhook.waitForEvent("sessionCreated", 250, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
// Now webhook response for event "sessionCreated" should be received
|
||||||
|
CustomWebhook.waitForEvent("sessionCreated", 750, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
this.sessionRestController.initializeConnection(sessionId, Map.of());
|
||||||
|
|
||||||
|
Session session = kurentoSessionManager.getSessionWithNotActive(sessionId);
|
||||||
|
Token token = new Token("token", sessionId, new ConnectionProperties.Builder().build(), null);
|
||||||
|
String participantPrivateId = "participantPrivateId";
|
||||||
|
Participant participant = kurentoSessionManager.newParticipant(session, participantPrivateId, token, null,
|
||||||
|
mock(GeoLocation.class), "platform", "finalUserId");
|
||||||
|
kurentoSessionManager.joinRoom(participant, sessionId, 1);
|
||||||
|
|
||||||
|
// Webhook event "participantJoined" is delayed 500 ms
|
||||||
|
// Expected TimeoutException
|
||||||
|
assertThrows(TimeoutException.class, () -> {
|
||||||
|
CustomWebhook.waitForEvent("participantJoined", 250, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client should have already received "connectionCreated" RPC response
|
||||||
|
// nonetheless
|
||||||
|
verify(sessionEventsHandler, times(1)).onParticipantJoined(refEq(participant), anyString(), anySet(),
|
||||||
|
anyInt(), refEq(null));
|
||||||
|
|
||||||
|
// Now webhook response for event "participantJoined" should be received
|
||||||
|
CustomWebhook.waitForEvent("participantJoined", 750, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
setHttpClientDelay(1);
|
||||||
|
// These events will be received immediately
|
||||||
|
this.sessionRestController.signal(Map.of("session", sessionId, "type", "1"));
|
||||||
|
this.sessionRestController.signal(Map.of("session", sessionId, "type", "2"));
|
||||||
|
setHttpClientDelay(500);
|
||||||
|
// This event will be received after a delay
|
||||||
|
this.sessionRestController.signal(Map.of("session", sessionId, "type", "3"));
|
||||||
|
setHttpClientDelay(1);
|
||||||
|
// These events should be received immediately after the delayed one
|
||||||
|
this.sessionRestController.signal(Map.of("session", sessionId, "type", "4"));
|
||||||
|
this.sessionRestController.signal(Map.of("session", sessionId, "type", "5"));
|
||||||
|
|
||||||
|
// RPC signal notification should have already been sent 5 times,
|
||||||
|
// no matter WebHook delays
|
||||||
|
verify(rpcNotificationService, times(5)).sendNotification(refEq(participantPrivateId),
|
||||||
|
refEq(ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD), any());
|
||||||
|
|
||||||
|
// Events received immediately
|
||||||
|
JsonObject signal1 = CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
JsonObject signal2 = CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
// Events not received due to timeout
|
||||||
|
assertThrows(TimeoutException.class, () -> {
|
||||||
|
CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
assertThrows(TimeoutException.class, () -> {
|
||||||
|
CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
// Events now received after timeout
|
||||||
|
JsonObject signal3 = CustomWebhook.waitForEvent("signalSent", 500, TimeUnit.MILLISECONDS);
|
||||||
|
JsonObject signal4 = CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
JsonObject signal5 = CustomWebhook.waitForEvent("signalSent", 25, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// Order of webhook events should be honored
|
||||||
|
Assert.assertEquals("Wrong signal type", "1", signal1.get("type").getAsString());
|
||||||
|
Assert.assertEquals("Wrong signal type", "2", signal2.get("type").getAsString());
|
||||||
|
Assert.assertEquals("Wrong signal type", "3", signal3.get("type").getAsString());
|
||||||
|
Assert.assertEquals("Wrong signal type", "4", signal4.get("type").getAsString());
|
||||||
|
Assert.assertEquals("Wrong signal type", "5", signal5.get("type").getAsString());
|
||||||
|
|
||||||
|
this.sessionRestController.closeConnection(sessionId, participant.getParticipantPublicId());
|
||||||
|
|
||||||
|
// Webhook is configured to receive "participantLeft" event
|
||||||
|
CustomWebhook.waitForEvent("participantLeft", 25, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// Webhook is NOT configured to receive "sessionDestroyed" event
|
||||||
|
assertThrows(TimeoutException.class, () -> {
|
||||||
|
CustomWebhook.waitForEvent("sessionDestroyed", 500, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
CustomWebhook.shutDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -64,11 +64,17 @@ public class CustomWebhook {
|
||||||
CustomWebhook.events.clear();
|
CustomWebhook.events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static JsonObject waitForEvent(String eventName, int maxSecondsWait) throws TimeoutException, InterruptedException {
|
public synchronized static JsonObject waitForEvent(String eventName, int maxSecondsWait)
|
||||||
|
throws TimeoutException, InterruptedException {
|
||||||
|
return CustomWebhook.waitForEvent(eventName, maxSecondsWait, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized static JsonObject waitForEvent(String eventName, int maxWait, TimeUnit timeUnit)
|
||||||
|
throws TimeoutException, InterruptedException {
|
||||||
if (events.get(eventName) == null) {
|
if (events.get(eventName) == null) {
|
||||||
events.put(eventName, new LinkedBlockingDeque<>());
|
events.put(eventName, new LinkedBlockingDeque<>());
|
||||||
}
|
}
|
||||||
JsonObject event = CustomWebhook.events.get(eventName).poll(maxSecondsWait, TimeUnit.SECONDS);
|
JsonObject event = CustomWebhook.events.get(eventName).poll(maxWait, timeUnit);
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
throw new TimeoutException("Timeout waiting for Webhook " + eventName);
|
throw new TimeoutException("Timeout waiting for Webhook " + eventName);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue