diff --git a/openvidu-test-e2e/pom.xml b/openvidu-test-e2e/pom.xml index 1b62c958..a33ecb69 100644 --- a/openvidu-test-e2e/pom.xml +++ b/openvidu-test-e2e/pom.xml @@ -163,6 +163,12 @@ openvidu-java-client 2.8.1 + + + com.mashape.unirest + unirest-java + 1.4.9 + diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 5e6c8747..b75e559c 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -44,6 +44,7 @@ import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import org.apache.commons.io.FileUtils; +import org.apache.http.HttpStatus; import org.jcodec.api.FrameGrab; import org.jcodec.api.JCodecException; import org.jcodec.common.model.Picture; @@ -75,19 +76,29 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.stream.JsonReader; +import com.mashape.unirest.http.HttpMethod; import io.github.bonigarcia.SeleniumExtension; import io.github.bonigarcia.wdm.WebDriverManager; +import io.openvidu.java.client.KurentoOptions; +import io.openvidu.java.client.MediaMode; import io.openvidu.java.client.OpenVidu; import io.openvidu.java.client.OpenViduHttpException; import io.openvidu.java.client.OpenViduJavaClientException; +import io.openvidu.java.client.OpenViduRole; import io.openvidu.java.client.Recording; +import io.openvidu.java.client.Recording.OutputMode; +import io.openvidu.java.client.RecordingMode; +import io.openvidu.java.client.Session; +import io.openvidu.java.client.SessionProperties; +import io.openvidu.java.client.TokenOptions; import io.openvidu.test.e2e.browser.BrowserUser; import io.openvidu.test.e2e.browser.ChromeAndroidUser; import io.openvidu.test.e2e.browser.ChromeUser; import io.openvidu.test.e2e.browser.FirefoxUser; import io.openvidu.test.e2e.browser.OperaUser; import io.openvidu.test.e2e.utils.CommandLineExecuter; +import io.openvidu.test.e2e.utils.CustomHttpClient; import io.openvidu.test.e2e.utils.MultimediaFileMetadata; import io.openvidu.test.e2e.utils.Unzipper; @@ -206,7 +217,9 @@ public class OpenViduTestAppE2eTest { @AfterEach void dispose() { - user.dispose(); + if (user != null) { + user.dispose(); + } Iterator it = otherUsers.iterator(); while (it.hasNext()) { BrowserUser other = it.next(); @@ -1898,6 +1911,153 @@ public class OpenViduTestAppE2eTest { gracefullyLeaveParticipants(2); } + @Test + @DisplayName("openvidu-java-client test") + void openViduJavaClientTest() throws Exception { + + setupBrowser("chrome"); + + log.info("openvidu-java-client test"); + + user.getDriver().findElement(By.id("one2one-btn")).click(); + + final String customSessionId = "openviduJavaClientSession"; + final String serverData1 = "SERVER_DATA_1"; + final String serverData2 = "SERVER_DATA_2"; + + Assert.assertFalse("OV.fetch() should return false if OV.createSession() has not been called", OV.fetch()); + List sessions = OV.getActiveSessions(); + Assert.assertEquals("Expected no active sessions but found " + sessions.size(), sessions.size(), 0); + + SessionProperties properties = new SessionProperties.Builder().customSessionId(customSessionId) + .mediaMode(MediaMode.ROUTED).recordingMode(RecordingMode.ALWAYS) + .defaultOutputMode(OutputMode.INDIVIDUAL).build(); + Session session = OV.createSession(properties); + + Assert.assertFalse("Session.fetch() should return false after OpenVidu.createSession()", session.fetch()); + Assert.assertFalse("OpenVidu.fetch() should return false after OpenVidu.createSession()", OV.fetch()); + sessions = OV.getActiveSessions(); + Assert.assertEquals("Expected 1 active session but found " + sessions.size(), 1, sessions.size()); + + KurentoOptions kurentoOptions = new KurentoOptions.Builder().videoMaxRecvBandwidth(250) + .allowedFilters(new String[] { "GStreamerFilter" }).build(); + TokenOptions tokenOptions1 = new TokenOptions.Builder().role(OpenViduRole.MODERATOR).data(serverData1) + .kurentoOptions(kurentoOptions).build(); + String token1 = session.generateToken(tokenOptions1); + + TokenOptions tokenOptions2 = new TokenOptions.Builder().role(OpenViduRole.SUBSCRIBER).data(serverData2).build(); + String token2 = session.generateToken(tokenOptions2); + + Assert.assertFalse("Session.fetch() should return false until a user has connected", session.fetch()); + + user.getDriver().findElement(By.id("session-settings-btn-0")).click(); + Thread.sleep(1000); + + // Set token 1 + WebElement tokeInput = user.getDriver().findElement(By.cssSelector("#custom-token-div input")); + tokeInput.clear(); + tokeInput.sendKeys(token1); + + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(1000); + + user.getDriver().findElement(By.id("session-settings-btn-1")).click(); + Thread.sleep(1000); + + // Set token 2 + tokeInput = user.getDriver().findElement(By.cssSelector("#custom-token-div input")); + tokeInput.clear(); + tokeInput.sendKeys(token2); + + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(1000); + + user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); + + user.getEventManager().waitUntilEventReaches("connectionCreated", 4); + user.getEventManager().waitUntilEventReaches("accessAllowed", 1); + user.getEventManager().waitUntilEventReaches("streamCreated", 2); + user.getEventManager().waitUntilEventReaches("streamPlaying", 2); + + final int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size(); + Assert.assertEquals("Expected 2 videos but found " + numberOfVideos, numberOfVideos, 2); + Assert.assertTrue("Videos were expected to have audio and video tracks", user.getEventManager() + .assertMediaTracks(user.getDriver().findElements(By.tagName("video")), true, true)); + + Assert.assertTrue("Session.fetch() should return true after users connected", session.fetch()); + + // Verify that users have the role and data they were assigned through + // TokenOptions + + gracefullyLeaveParticipants(2); + } + + @Test + @DisplayName("REST API test") + void restApiTest() throws Exception { + + CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, OPENVIDU_SECRET); + + // 401 + restClient.testAuthorizationError(); + + // GET /api/sessions before any session created + restClient.rest(HttpMethod.GET, "/api/sessions/NOT_EXISTS", HttpStatus.SC_NOT_FOUND); + Map returnValues = new HashMap<>(); + returnValues.put("numberOfElements", new Integer(0)); + returnValues.put("content", "[]"); + restClient.rest(HttpMethod.GET, "/api/sessions", null, HttpStatus.SC_OK, true, returnValues); + + /** POST /api/sessions **/ + // 400 + String body = "{'mediaMode': 'ROUTED', 'recordingMode': 'ALWAYS', 'customSessionId': 999}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + body = "{'mediaMode': 'ROUTED', 'recordingMode': false}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + body = "{'mediaMode': 'NOT_EXISTS'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + body = "{'mediaMode': 'ROUTED', 'recordingMode': 'NOT_EXISTS'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + body = "{'mediaMode': 'ROUTED', 'recordingMode': 'ALWAYS', 'defaultOutputMode': 'NOT_EXISTS'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + body = "{'mediaMode': 'ROUTED', 'recordingMode': 'ALWAYS', 'defaultOutputMode': 'INDIVIDUAL', 'defaultRecordingLayout': 'NOT_EXISTS'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_BAD_REQUEST); + + // 200 + body = "{'mediaMode': 'ROUTED', 'recordingMode': 'ALWAYS', 'customSessionId': 'CUSTOM_SESSION_ID', 'defaultOutputMode': 'INDIVIDUAL', 'defaultRecordingLayout': 'BEST_FIT'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_OK, true, + "{'id': 'STR', 'createdAt': 0}"); + + // 409 + body = "{'customSessionId': 'CUSTOM_SESSION_ID'}"; + restClient.rest(HttpMethod.POST, "/api/sessions", body, HttpStatus.SC_CONFLICT); + + // GET /api/sessions after session created + restClient.rest(HttpMethod.GET, "/api/sessions/CUSTOM_SESSION_ID", null, HttpStatus.SC_OK, true, + "{'sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultOutputMode':'STR','customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':true}"); + returnValues = new HashMap<>(); + returnValues.put("numberOfElements", new Integer(1)); + returnValues.put("content", new org.json.JSONArray()); + restClient.rest(HttpMethod.GET, "/api/sessions", null, HttpStatus.SC_OK, true, returnValues); + + /** POST /api/tokens **/ + // 400 + body = "{'session': true}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_BAD_REQUEST); + body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'NOT_EXISTS', 'data': 'DATA'}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_BAD_REQUEST); + body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'MODERATOR', 'data': 999}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_BAD_REQUEST); + body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'MODERATOR', 'data': 'SERVER_DATA', 'kurentoOptions': false}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_BAD_REQUEST); + body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'MODERATOR', 'data': 'SERVER_DATA', 'kurentoOptions': {'allowedFilters': 'NOT_EXISTS'}}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_BAD_REQUEST); + + // 200 + body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'MODERATOR', 'data': 'SERVER_DATA', 'kurentoOptions': {'allowedFilters': ['GStreamerFilter']}}"; + restClient.rest(HttpMethod.POST, "/api/tokens", body, HttpStatus.SC_OK); + } + private void listEmptyRecordings() { // List existing recordings (empty) user.getDriver().findElement(By.id("list-recording-btn")).click(); diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomHttpClient.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomHttpClient.java new file mode 100644 index 00000000..cbc31681 --- /dev/null +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomHttpClient.java @@ -0,0 +1,217 @@ +/* + * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.openvidu.test.e2e.utils; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Map; +import java.util.Map.Entry; + +import javax.net.ssl.SSLContext; + +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.simple.parser.JSONParser; +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mashape.unirest.http.HttpMethod; +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.JsonNode; +import com.mashape.unirest.http.Unirest; +import com.mashape.unirest.http.exceptions.UnirestException; +import com.mashape.unirest.request.HttpRequest; +import com.mashape.unirest.request.HttpRequestWithBody; + +public class CustomHttpClient { + + private static final Logger log = LoggerFactory.getLogger(CustomHttpClient.class); + + private String openviduUrl; + private String headerAuth; + + private JSONParser parser = new JSONParser(); + + public CustomHttpClient(String openviduUrl, String openviduSecret) { + this.openviduUrl = openviduUrl.replaceFirst("/*$", ""); + this.headerAuth = "Basic " + Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + openviduSecret).getBytes()); + + SSLContext sslContext = null; + try { + sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy() { + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + return true; + } + }).build(); + } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { + Assert.fail("Error building custom HttpClient: " + e.getMessage()); + } + HttpClient unsafeHttpClient = HttpClients.custom().setSSLContext(sslContext) + .setSSLHostnameVerifier(new NoopHostnameVerifier()).build(); + Unirest.setHttpClient(unsafeHttpClient); + } + + public void testAuthorizationError() { + try { + String wrongCredentials = "Basic " + + Base64.getEncoder().encodeToString(("OPENVIDUAPP:WRONG_SECRET").getBytes()); + Assert.assertEquals("Expected 401 status (unauthorized)", HttpStatus.SC_UNAUTHORIZED, Unirest + .get(openviduUrl + "/config").header("Authorization", wrongCredentials).asJson().getStatus()); + } catch (UnirestException e) { + Assert.fail("Error testing UNAUTHORIZED rest method: " + e.getMessage()); + } + } + + public void rest(HttpMethod method, String path, int status) { + this.commonRest(method, path, null, status); + } + + public void rest(HttpMethod method, String path, String body, int status) { + this.commonRest(method, path, body, status); + } + + public void rest(HttpMethod method, String path, String body, int status, boolean exactFields, + String jsonElmentString) { + JSONObject json = this.commonRest(method, path, body, status); + JSONObject jsonObjExpected = null; + jsonElmentString.replaceAll("'", "\""); + try { + jsonObjExpected = new JSONObject((String) jsonElmentString); + } catch (JSONException e1) { + Assert.fail("Expected json element is a string without a JSON format: " + jsonElmentString); + } + + if (exactFields) { + Assert.assertEquals("Error in number of keys in JSON response to POST " + path, jsonObjExpected.length(), + json.length()); + } + for (String key : jsonObjExpected.keySet()) { + json.get(key); + } + } + + public void rest(HttpMethod method, String path, String body, int status, boolean exactFields, + Map jsonResponse) { + org.json.JSONObject json = this.commonRest(method, path, body, status); + + if (exactFields) { + Assert.assertEquals("Error in number of keys in JSON response to POST " + path, jsonResponse.size(), + json.length()); + } + + for (Entry entry : jsonResponse.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof String) { + try { + JSONObject jsonObjExpected = new JSONObject((String) value); + JSONObject jsonObjActual = json.getJSONObject(entry.getKey()); + // COMPARE + + } catch (JSONException e1) { + try { + JSONArray jsonArrayExpected = new JSONArray((String) value); + JSONArray jsonArrayActual = json.getJSONArray(entry.getKey()); + // COMPARE + + } catch (JSONException e2) { + Assert.assertEquals("JSON field " + entry.getKey() + " has not expected value", (String) value, + json.getInt(entry.getKey())); + } + } + } else if (value instanceof Integer) { + Assert.assertEquals("JSON field " + entry.getKey() + " has not expected value", (int) value, + json.getInt(entry.getKey())); + } else if (value instanceof Long) { + Assert.assertEquals("JSON field " + entry.getKey() + " has not expected value", (long) value, + json.getLong(entry.getKey())); + } else if (value instanceof Double) { + Assert.assertEquals("JSON field " + entry.getKey() + " has not expected value", (double) value, + json.getDouble(entry.getKey()), 0.001); + } else if (value instanceof Boolean) { + Assert.assertEquals("JSON field " + entry.getKey() + " has not expected value", (boolean) value, + json.getBoolean(entry.getKey())); + } else if (value instanceof JSONArray) { + json.getJSONArray(entry.getKey()); + } else if (value instanceof JSONObject) { + json.getJSONObject(entry.getKey()); + } else { + Assert.fail("JSON response field cannot be parsed: " + entry.toString()); + } + } + } + + private org.json.JSONObject commonRest(HttpMethod method, String path, String body, int status) { + HttpResponse jsonResponse = null; + org.json.JSONObject json = null; + path = openviduUrl + (path.startsWith("/") ? path : ("/" + path)); + + HttpRequest request = null; + if (body != null && !body.isEmpty()) { + switch (method) { + case POST: + request = Unirest.post(path); + break; + case PUT: + request = Unirest.put(path); + break; + default: + break; + } + ((HttpRequestWithBody) request).header("Content-Type", "application/json").body(body.replaceAll("'", "\"")); + } else { + switch (method) { + case GET: + request = Unirest.get(path); + break; + case POST: + request = Unirest.post(path); + break; + case DELETE: + request = Unirest.delete(path); + break; + default: + break; + } + request.header("Content-Type", "application/x-www-form-urlencoded"); + } + try { + jsonResponse = request.header("Authorization", this.headerAuth).asJson(); + if (jsonResponse.getBody() != null) { + json = jsonResponse.getBody().getObject(); + } + } catch (UnirestException e) { + log.error(e.getMessage()); + Assert.fail("Error sending request to " + path + ": " + e.getMessage()); + } + Assert.assertEquals(path + " expected to return status " + status, status, jsonResponse.getStatus()); + return json; + } +}