From 212cb3e35d1ec38b47afd5a186f8d7b8db669c08 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Wed, 28 Jun 2023 16:16:14 +0100 Subject: [PATCH] Meetings API (#434) * Added MeetingsClient stub * Added api-eu base URI * Added endpoints & types * Added _links * Added missing fields for room * Format createdAt & expiresAt * Added validation to MeetingRoom * Added documentation for top-level methods * Validate theme colour * Some fixes to ser/des properties * Added test stub utilities * Added test for getRoom * Comprehensive MeetingRoomTest * Hide UrlContainer nesting from user * Bumped sample expiration year * Param validation in MeetingsClient * Use UUID in MeetingsClient methods * GetRooms client test * expireAfterUse validation * Fixed UpdateApplicationRequest * Use ListX for plural endpoints * App & recording ID now UUIDs * Rename to match conventions * Added simplified listRooms call * Added Recordings test * Test listLogoUploadUrls * Use Instant instead of ZonedDateTime * More tests * More tests & edge case coverage * UpdateRoom/UpdateTheme tests * Use enum for UISettings.Language * Added ThemeTest * Fixed updateTheme & added uploadLogo * Added uploadLogo method & wrapper * Updated MeetingRoom.expiresAt builder * Updated BugRepro * Fixed failing test * Various fixes & improvements * 100% coverage * Removed /beta from URLs * Added MeetingsResponseException * Made SelfReferencing final * Added webhook * Improved VonageApiResponseException coverage * Hydrate MeetingRoom on creation response * Hydrate Theme * Recurse through ListRooms response * Added throws to MeetingsClient doc * Clarified thread safety FAQ * More fixes * Renamed setThemeLogo --- .github/workflows/publish.yml | 2 +- .gitignore | 1 - CHANGELOG.md | 1 + README.md | 11 +- build.gradle | 5 +- .../client/VonageApiResponseException.java | 35 +- .../java/com/vonage/client/VonageClient.java | 12 + .../vonage/client/meetings/Application.java | 78 ++ .../client/meetings/AvailableFeatures.java | 144 ++++ .../vonage/client/meetings/CallbackUrls.java | 130 +++ .../client/meetings/CreateRoomEndpoint.java | 72 ++ .../client/meetings/CreateThemeEndpoint.java | 72 ++ .../meetings/DeleteRecordingEndpoint.java | 57 ++ .../client/meetings/DeleteThemeEndpoint.java | 57 ++ .../client/meetings/DeleteThemeRequest.java | 28 + .../vonage/client/meetings/DialInNumber.java | 62 ++ .../com/vonage/client/meetings/EventType.java | 69 ++ .../meetings/FinalizeLogosEndpoint.java | 59 ++ .../client/meetings/FinalizeLogosRequest.java | 46 + .../meetings/GetLogoUploadUrlsEndpoint.java | 69 ++ .../client/meetings/GetRecordingEndpoint.java | 55 ++ .../client/meetings/GetRoomEndpoint.java | 55 ++ .../client/meetings/GetThemeEndpoint.java | 55 ++ .../client/meetings/InitialJoinOptions.java | 79 ++ .../client/meetings/JoinApprovalLevel.java | 55 ++ .../meetings/ListDialNumbersEndpoint.java | 59 ++ .../meetings/ListRecordingsEndpoint.java | 58 ++ .../meetings/ListRecordingsResponse.java | 48 ++ .../client/meetings/ListRoomsEndpoint.java | 55 ++ .../client/meetings/ListRoomsRequest.java | 44 + .../client/meetings/ListRoomsResponse.java | 59 ++ .../client/meetings/ListThemesEndpoint.java | 63 ++ .../com/vonage/client/meetings/LogoType.java | 44 + .../meetings/LogoUploadsUrlResponse.java | 144 ++++ .../vonage/client/meetings/MeetingRoom.java | 488 +++++++++++ .../client/meetings/MeetingsClient.java | 430 ++++++++++ .../meetings/MeetingsEventCallback.java | 211 +++++ .../meetings/MeetingsResponseException.java | 79 ++ .../client/meetings/MicrophoneState.java | 44 + .../com/vonage/client/meetings/Recording.java | 116 +++ .../client/meetings/RecordingLinks.java | 35 + .../client/meetings/RecordingOptions.java | 101 +++ .../client/meetings/RecordingStatus.java | 45 + .../vonage/client/meetings/RoomLanguage.java | 80 ++ .../com/vonage/client/meetings/RoomLinks.java | 43 + .../com/vonage/client/meetings/RoomType.java | 51 ++ .../meetings/SearchThemeRoomsEndpoint.java | 56 ++ .../com/vonage/client/meetings/Theme.java | 336 ++++++++ .../vonage/client/meetings/ThemeDomain.java | 36 + .../vonage/client/meetings/UISettings.java | 78 ++ .../meetings/UpdateApplicationEndpoint.java | 59 ++ .../meetings/UpdateApplicationRequest.java | 95 +++ .../client/meetings/UpdateRoomEndpoint.java | 60 ++ .../client/meetings/UpdateRoomRequest.java | 245 ++++++ .../client/meetings/UpdateThemeEndpoint.java | 74 ++ .../vonage/client/meetings/UrlContainer.java | 36 + .../vonage/client/meetings/package-info.java | 26 + .../ProactiveConnectClient.java | 4 +- .../ProactiveConnectResponseException.java | 14 - .../client/proactiveconnect/package-info.java | 2 +- .../client/verify2/VerificationCallback.java | 3 +- src/test/java/com/vonage/client/BugRepro.java | 23 +- .../java/com/vonage/client/ClientTest.java | 11 +- .../VonageApiResponseExceptionTest.java | 33 +- .../com/vonage/client/VonageClientTest.java | 1 + .../meetings/CreateRoomEndpointTest.java | 132 +++ .../meetings/CreateThemeEndpointTest.java | 90 ++ .../meetings/DeleteRecordingEndpointTest.java | 63 ++ .../meetings/DeleteThemeEndpointTest.java | 63 ++ .../meetings/FinalizeLogosEndpointTest.java | 79 ++ .../GetLogoUploadUrlsEndpointTest.java | 104 +++ .../meetings/GetRecordingEndpointTest.java | 81 ++ .../client/meetings/GetRoomEndpointTest.java | 68 ++ .../client/meetings/GetThemeEndpointTest.java | 70 ++ .../meetings/ListDialNumbersEndpointTest.java | 88 ++ .../meetings/ListRecordingsEndpointTest.java | 125 +++ .../meetings/ListRoomsEndpointTest.java | 84 ++ .../meetings/ListThemesEndpointTest.java | 78 ++ .../client/meetings/MeetingRoomTest.java | 343 ++++++++ .../client/meetings/MeetingsClientTest.java | 786 ++++++++++++++++++ .../meetings/MeetingsEventCallbackTest.java | 293 +++++++ .../SearchThemeRoomsEndpointTest.java | 87 ++ .../com/vonage/client/meetings/ThemeTest.java | 211 +++++ .../UpdateApplicationEndpointTest.java | 98 +++ .../meetings/UpdateRoomEndpointTest.java | 102 +++ .../meetings/UpdateThemeEndpointTest.java | 89 ++ .../client/messages/MessageRequestTest.java | 2 +- .../client/messages/MessageStatusTest.java | 2 +- .../proactiveconnect/ContactsListTest.java | 2 +- .../CreateSubaccountRequestTest.java | 2 +- .../subaccounts/NumberTransferTest.java | 2 +- .../UpdateSubaccountRequestTest.java | 2 +- .../verify2/VerificationRequestTest.java | 2 +- .../verify2/VerifyCodeEndpointTest.java | 2 +- 94 files changed, 7895 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/vonage/client/meetings/Application.java create mode 100644 src/main/java/com/vonage/client/meetings/AvailableFeatures.java create mode 100644 src/main/java/com/vonage/client/meetings/CallbackUrls.java create mode 100644 src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java create mode 100644 src/main/java/com/vonage/client/meetings/DialInNumber.java create mode 100644 src/main/java/com/vonage/client/meetings/EventType.java create mode 100644 src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java create mode 100644 src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/InitialJoinOptions.java create mode 100644 src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java create mode 100644 src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java create mode 100644 src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/ListRoomsRequest.java create mode 100644 src/main/java/com/vonage/client/meetings/ListRoomsResponse.java create mode 100644 src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/LogoType.java create mode 100644 src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java create mode 100644 src/main/java/com/vonage/client/meetings/MeetingRoom.java create mode 100644 src/main/java/com/vonage/client/meetings/MeetingsClient.java create mode 100644 src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java create mode 100644 src/main/java/com/vonage/client/meetings/MeetingsResponseException.java create mode 100644 src/main/java/com/vonage/client/meetings/MicrophoneState.java create mode 100644 src/main/java/com/vonage/client/meetings/Recording.java create mode 100644 src/main/java/com/vonage/client/meetings/RecordingLinks.java create mode 100644 src/main/java/com/vonage/client/meetings/RecordingOptions.java create mode 100644 src/main/java/com/vonage/client/meetings/RecordingStatus.java create mode 100644 src/main/java/com/vonage/client/meetings/RoomLanguage.java create mode 100644 src/main/java/com/vonage/client/meetings/RoomLinks.java create mode 100644 src/main/java/com/vonage/client/meetings/RoomType.java create mode 100644 src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/Theme.java create mode 100644 src/main/java/com/vonage/client/meetings/ThemeDomain.java create mode 100644 src/main/java/com/vonage/client/meetings/UISettings.java create mode 100644 src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java create mode 100644 src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java create mode 100644 src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java create mode 100644 src/main/java/com/vonage/client/meetings/UrlContainer.java create mode 100644 src/main/java/com/vonage/client/meetings/package-info.java create mode 100644 src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/MeetingRoomTest.java create mode 100644 src/test/java/com/vonage/client/meetings/MeetingsClientTest.java create mode 100644 src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java create mode 100644 src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/ThemeTest.java create mode 100644 src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java create mode 100644 src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 367bb9638..05909e3b1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle - - name: Publish with gradle + - name: Publish with Gradle env: signingKey: ${{secrets.SIGNING_KEY}} signingPassword: ${{secrets.SIGNING_PASSWORD}} diff --git a/.gitignore b/.gitignore index f3b6b5586..0e8afb0ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -src/test/java/com/vonage/client/BugRepro.java *.groovy .classpath .gradle diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f002399..848622d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [7.6.0] - 2023-06-29 - Added Proactive Connect API implementation +- Added Meetings API implementation - Updated Subaccounts name & secret validation logic # [7.5.0] - 2023-06-14 diff --git a/README.md b/README.md index 77e36c94f..ef9da99ca 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ to your project's classpath. * There are also **many useful code samples** in our [nexmo-community/nexmo-java-quickstart](https://github.com/nexmo-community/nexmo-java-quickstart) repository. ### Customize the Base URI -By default, the client will use https://api.nexmo.com, https://rest.nexmo.com, and https://sns.nexmo.com as base URIs for the various endpoints. To customize these you can instantiate `VonageClient` with an `HttpConfig` object. +By default, the client will use https://api.nexmo.com, https://rest.nexmo.com, https://sns.nexmo.com and https://api-eu.vonage.com as base URIs for the various endpoints. To customize these you can instantiate `VonageClient` with an `HttpConfig` object. `HttpConfig.Builder` has been created to assist in building this object. Usage is as follows: @@ -97,6 +97,7 @@ HttpConfig httpConfig = HttpConfig.builder() .apiBaseUri("https://api.example.com") .restBaseUri("https://rest.example.com") .snsBaseUri("https://sns.example.com") + .apiEuBaseUri("https://api-eu.example.com") .build(); VonageClient client = VonageClient.builder() @@ -524,8 +525,11 @@ Our [Voice API](https://developer.vonage.com/voice/voice-api/overview) can conne ## Frequently Asked Questions -Q: Does this SDK support thread safety? -A: No, it currently does not. +Q: What is your policy on thread safety? + +A: The current architecture of the SDK means that only one thread should use the client at a time. +If you would like to use the SDK in a multithreaded environment, create a separate instance of +`VonageClient` for each thread. Q: Does this SDK support asynchronous request / response processing? A: Currently no, but it is on the roadmap. @@ -544,6 +548,7 @@ The following is a list of Vonage APIs and whether the Java SDK provides support | Dispatch | Beta | ❌ | | External Accounts | Developer Preview | ❌ | | Media | Beta | ❌ | +| Meetings | General Availability | ✅ | | Messages | General Availability | ✅ | | Number Insight | General Availability | ✅ | | Number Management | General Availability | ✅ | diff --git a/build.gradle b/build.gradle index 38912f4b2..9d5f1cd56 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'commons-codec:commons-codec:1.15' implementation 'org.apache.httpcomponents:httpclient:4.5.14' + implementation 'org.apache.httpcomponents:httpmime:4.5.14' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' implementation 'io.openapitools.jackson.dataformat:jackson-dataformat-hal:1.0.9' @@ -36,8 +37,8 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:4.11.0' testImplementation 'org.hamcrest:hamcrest-all:1.3' - testImplementation 'org.springframework:spring-test:5.3.27' - testImplementation 'org.springframework:spring-web:5.3.27' + testImplementation 'org.springframework:spring-test:5.3.28' + testImplementation 'org.springframework:spring-web:5.3.28' testImplementation 'jakarta.servlet:jakarta.servlet-api:4.0.4' } diff --git a/src/main/java/com/vonage/client/VonageApiResponseException.java b/src/main/java/com/vonage/client/VonageApiResponseException.java index ef993b194..08a3b2724 100644 --- a/src/main/java/com/vonage/client/VonageApiResponseException.java +++ b/src/main/java/com/vonage/client/VonageApiResponseException.java @@ -27,6 +27,7 @@ import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.URI; +import java.util.List; import java.util.Objects; /** @@ -38,8 +39,24 @@ public abstract class VonageApiResponseException extends VonageClientException { protected URI type; protected String title, detail, instance; + protected List errors; @JsonIgnore protected int statusCode; + protected VonageApiResponseException() { + } + + protected VonageApiResponseException(String message) { + super(message); + } + + protected VonageApiResponseException(String message, Throwable cause) { + super(message, cause); + } + + protected VonageApiResponseException(Throwable cause) { + super(cause); + } + /** * Link to the API error type. * @@ -80,6 +97,18 @@ public String getInstance() { return instance; } + /** + * Additional description of problems encountered with the request. + * This is typically only applicable to 400 or 409 error codes. + * + * @return The list of errors returned from the server (could be a Map or String), + * or {@code null} if none / not applicable. + */ + @JsonProperty("errors") + public List getErrors() { + return errors; + } + /** * The API response status code, usually 4xx or 500. * @@ -145,9 +174,9 @@ public String toJson() { protected static E fromJson(Class clazz, String json) { if (json == null || json.length() < 2) { try { - return clazz.newInstance(); + return clazz.getConstructor().newInstance(); } - catch (InstantiationException | IllegalAccessException ex) { + catch (Exception ex) { throw new VonageUnexpectedException(ex); } } @@ -156,7 +185,7 @@ protected static E fromJson(Class claz return mapper.readValue(json, clazz); } catch (IOException ex) { - throw new VonageUnexpectedException("Failed to produce "+clazz.getSimpleName()+" from json.", ex); + throw new VonageResponseParseException("Failed to produce "+clazz.getSimpleName()+" from json.", ex); } } diff --git a/src/main/java/com/vonage/client/VonageClient.java b/src/main/java/com/vonage/client/VonageClient.java index 20d68a38f..f71608cff 100644 --- a/src/main/java/com/vonage/client/VonageClient.java +++ b/src/main/java/com/vonage/client/VonageClient.java @@ -21,6 +21,7 @@ import com.vonage.client.auth.hashutils.HashUtil; import com.vonage.client.conversion.ConversionClient; import com.vonage.client.insight.InsightClient; +import com.vonage.client.meetings.MeetingsClient; import com.vonage.client.messages.MessagesClient; import com.vonage.client.numbers.NumbersClient; import com.vonage.client.proactiveconnect.ProactiveConnectClient; @@ -62,6 +63,7 @@ public class VonageClient { private final Verify2Client verify2; private final SubaccountsClient subaccounts; private final ProactiveConnectClient proactiveConnect; + private final MeetingsClient meetings; private VonageClient(Builder builder) { httpWrapper = new HttpWrapper(builder.httpConfig, builder.authCollection); @@ -81,6 +83,7 @@ private VonageClient(Builder builder) { verify2 = new Verify2Client(httpWrapper); subaccounts = new SubaccountsClient(httpWrapper); proactiveConnect = new ProactiveConnectClient(httpWrapper); + meetings = new MeetingsClient(httpWrapper); } public AccountClient getAccountClient() { @@ -147,6 +150,15 @@ public ProactiveConnectClient getProactiveConnectClient() { return proactiveConnect; } + /** + * + * @return The Meetings client. + * @since 7.6.0 + */ + public MeetingsClient getMeetingsClient() { + return meetings; + } + /** * * @return The Verify v2 client. diff --git a/src/main/java/com/vonage/client/meetings/Application.java b/src/main/java/com/vonage/client/meetings/Application.java new file mode 100644 index 000000000..17e5d422f --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/Application.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.VonageResponseParseException; +import java.io.IOException; +import java.util.UUID; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Application { + private String accountId; + private UUID applicationId, defaultThemeId; + + protected Application() { + } + + /** + * ID of the application. + * + * @return The application ID. + */ + @JsonProperty("application_id") + public UUID getApplicationId() { + return applicationId; + } + + /** + * ID of the account application. + * + * @return The account ID. + */ + @JsonProperty("account_id") + public String getAccountId() { + return accountId; + } + + /** + * ID of the default theme. + * + * @return The application default theme ID. + */ + @JsonProperty("default_theme_id") + public UUID getDefaultThemeId() { + return defaultThemeId; + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with the fields populated, if present. + */ + public static Application fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, Application.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce Application from json.", ex); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/AvailableFeatures.java b/src/main/java/com/vonage/client/meetings/AvailableFeatures.java new file mode 100644 index 000000000..9476490bd --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/AvailableFeatures.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AvailableFeatures { + private Boolean isRecordingAvailable, isChatAvailable, isWhiteboardAvailable, isLocaleSwitcherAvailable; + + protected AvailableFeatures() { + } + + AvailableFeatures(Builder builder) { + isRecordingAvailable = builder.isRecordingAvailable; + isChatAvailable = builder.isChatAvailable; + isWhiteboardAvailable = builder.isWhiteboardAvailable; + isLocaleSwitcherAvailable = builder.isLocaleSwitcherAvailable; + } + + /** + * Determine if recording feature is available in the UI. + * + * @return {@code true} if the feature is available. + */ + @JsonProperty("is_recording_available") + public Boolean getIsRecordingAvailable() { + return isRecordingAvailable; + } + + /** + * Determine if chat feature is available in the UI. + * + * @return {@code true} if the feature is available. + */ + @JsonProperty("is_chat_available") + public Boolean getIsChatAvailable() { + return isChatAvailable; + } + + /** + * Determine if whiteboard feature is available in the UI. + * + * @return {@code true} if the feature is available. + */ + @JsonProperty("is_whiteboard_available") + public Boolean getIsWhiteboardAvailable() { + return isWhiteboardAvailable; + } + + /** + * Determine if locale switcher feature is available in the UI. + * + * @return {@code true} if the feature is available. + */ + @JsonProperty("is_locale_switcher_available") + public Boolean getIsLocaleSwitcherAvailable() { + return isLocaleSwitcherAvailable; + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Boolean isRecordingAvailable, isChatAvailable, isWhiteboardAvailable, isLocaleSwitcherAvailable; + + Builder() {} + + /** + * + * @param isRecordingAvailable Determine if recording feature is available in the UI. + * + * @return This builder. + */ + public Builder isRecordingAvailable(boolean isRecordingAvailable) { + this.isRecordingAvailable = isRecordingAvailable; + return this; + } + + /** + * + * @param isChatAvailable Determine if chat feature is available in the UI. + * + * @return This builder. + */ + public Builder isChatAvailable(boolean isChatAvailable) { + this.isChatAvailable = isChatAvailable; + return this; + } + + /** + * + * @param isWhiteboardAvailable Determine if whiteboard feature is available in the UI. + * + * @return This builder. + */ + public Builder isWhiteboardAvailable(boolean isWhiteboardAvailable) { + this.isWhiteboardAvailable = isWhiteboardAvailable; + return this; + } + + /** + * + * @param isLocaleSwitcherAvailable Determine if locale switcher feature is available in the UI. + * + * @return This builder. + */ + public Builder isLocaleSwitcherAvailable(boolean isLocaleSwitcherAvailable) { + this.isLocaleSwitcherAvailable = isLocaleSwitcherAvailable; + return this; + } + + /** + * Builds the {@linkplain AvailableFeatures}. + * + * @return An instance of AvailableFeatures, populated with all fields from this builder. + */ + public AvailableFeatures build() { + return new AvailableFeatures(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/CallbackUrls.java b/src/main/java/com/vonage/client/meetings/CallbackUrls.java new file mode 100644 index 000000000..f1efdc0bb --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/CallbackUrls.java @@ -0,0 +1,130 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallbackUrls { + private URI roomsCallbackUrl, sessionsCallbackUrl, recordingsCallbackUrl; + + protected CallbackUrls() { + } + + CallbackUrls(Builder builder) { + if (builder.roomsCallbackUrl != null) { + roomsCallbackUrl = URI.create(builder.roomsCallbackUrl); + } + if (builder.sessionsCallbackUrl != null) { + sessionsCallbackUrl = URI.create(builder.sessionsCallbackUrl); + } + if (builder.recordingsCallbackUrl != null) { + recordingsCallbackUrl = URI.create(builder.recordingsCallbackUrl); + } + } + + /** + * Callback url for rooms events, overrides application level rooms callback URL. + * + * @return The rooms callback URL. + */ + @JsonProperty("rooms_callback_url") + public URI getRoomsCallbackUrl() { + return roomsCallbackUrl; + } + + /** + * Callback url for sessions events, overrides application level sessions callback URL. + * + * @return The sessions callback URL. + */ + @JsonProperty("sessions_callback_url") + public URI getSessionsCallbackUrl() { + return sessionsCallbackUrl; + } + + /** + * Callback url for recordings events, overrides application level recordings callback URL. + * + * @return The recordings callback URL. + */ + @JsonProperty("recordings_callback_url") + public URI getRecordingsCallbackUrl() { + return recordingsCallbackUrl; + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String roomsCallbackUrl, sessionsCallbackUrl, recordingsCallbackUrl; + + Builder() {} + + /** + * + * @param roomsCallbackUrl Callback url for rooms events, overrides application level rooms callback url. + * + * @return This builder. + */ + public Builder roomsCallbackUrl(String roomsCallbackUrl) { + this.roomsCallbackUrl = roomsCallbackUrl; + return this; + } + + /** + * + * @param sessionsCallbackUrl Callback url for sessions events, overrides application level sessions callback url. + * + * @return This builder. + */ + public Builder sessionsCallbackUrl(String sessionsCallbackUrl) { + this.sessionsCallbackUrl = sessionsCallbackUrl; + return this; + } + + /** + * + * @param recordingsCallbackUrl Callback url for recordings events, overrides application level recordings callback url. + * + * @return This builder. + */ + public Builder recordingsCallbackUrl(String recordingsCallbackUrl) { + this.recordingsCallbackUrl = recordingsCallbackUrl; + return this; + } + + + /** + * Builds the {@linkplain CallbackUrls}. + * + * @return An instance of CallbackUrls, populated with all fields from this builder. + */ + public CallbackUrls build() { + return new CallbackUrls(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java new file mode 100644 index 000000000..1e205a4c2 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import java.io.IOException; + +class CreateRoomEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/rooms"; + private MeetingRoom cachedRoom; + + CreateRoomEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(MeetingRoom request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.post(uri) + .setHeader("Content-Type", "application/json") + .setHeader("Accept", "application/json") + .setEntity(new StringEntity((cachedRoom = request).toJson(), ContentType.APPLICATION_JSON)); + } + + @Override + public MeetingRoom parseResponse(HttpResponse response) throws IOException { + try { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (cachedRoom != null) { + cachedRoom.updateFromJson(json); + return cachedRoom; + } + else { + return MeetingRoom.fromJson(json); + } + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } + finally { + cachedRoom = null; + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java new file mode 100644 index 000000000..7319f4e01 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class CreateThemeEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes"; + private Theme cachedTheme; + + CreateThemeEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(Theme request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.post(uri) + .setHeader("Content-Type", "application/json") + .setHeader("Accept", "application/json") + .setEntity(new StringEntity((cachedTheme = request).toJson(), ContentType.APPLICATION_JSON)); + } + + @Override + public Theme parseResponse(HttpResponse response) throws IOException { + try { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (cachedTheme != null) { + cachedTheme.updateFromJson(json); + return cachedTheme; + } + else { + return Theme.fromJson(json); + } + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } + finally { + cachedTheme = null; + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java b/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java new file mode 100644 index 000000000..392e74cde --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.UUID; + +class DeleteRecordingEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/recordings/%s"; + + DeleteRecordingEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UUID request) { + String path = String.format(PATH, request); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + return RequestBuilder.delete(uri) + .setHeader("Content-Type", "application/json"); + } + + @Override + public Void parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 204) { + return null; + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java new file mode 100644 index 000000000..d2ddec538 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class DeleteThemeEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/%s"; + + DeleteThemeEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(DeleteThemeRequest request) { + String path = String.format(PATH, request.themeId); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + return RequestBuilder.delete(uri) + .addParameter("force", String.valueOf(request.force)) + .setHeader("Content-Type", "application/json"); + } + + @Override + public Void parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 204) { + return null; + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java b/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java new file mode 100644 index 000000000..f519b8769 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import java.util.UUID; + +class DeleteThemeRequest { + final UUID themeId; + final boolean force; + + DeleteThemeRequest(UUID themeId, boolean force) { + this.themeId = themeId; + this.force = force; + } +} diff --git a/src/main/java/com/vonage/client/meetings/DialInNumber.java b/src/main/java/com/vonage/client/meetings/DialInNumber.java new file mode 100644 index 000000000..d3ad3bda7 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/DialInNumber.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Locale; + +/** + * Details about a number that that can be used to dial into a meeting. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class DialInNumber { + private String number, displayName; + private Locale locale; + + protected DialInNumber() { + } + + /** + * The dial-in number. + * + * @return The number in E164 format. + */ + @JsonProperty("number") + public String getNumber() { + return number; + } + + /** + * The number locale. + * + * @return The Locale. + */ + @JsonProperty("locale") + public Locale getLocale() { + return locale; + } + + /** + * The country name of the locale. + * + * @return The country's name. + */ + @JsonProperty("display_name") + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/com/vonage/client/meetings/EventType.java b/src/main/java/com/vonage/client/meetings/EventType.java new file mode 100644 index 000000000..30a62b5cc --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/EventType.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the event type in {@link MeetingsEventCallback#getEvent()}. + */ +public enum EventType { + /** + * Room expired. + */ + ROOM_EXPIRED, + + /** + * Session started. + */ + SESSION_STARTED, + + /** + * Session ended. + */ + SESSION_ENDED, + + /** + * Recording started. + */ + RECORDING_STARTED, + + /** + * Recording ended. + */ + RECORDING_ENDED, + + /** + * Recording uploaded. + */ + RECORDING_UPLOADED, + + /** + * Participant joined. + */ + SESSION_PARTICIPANT_JOINED, + + /** + * Participant left. + */ + SESSION_PARTICIPANT_LEFT; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase().replace('_', ':'); + } +} diff --git a/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java b/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java new file mode 100644 index 000000000..2fc529ed1 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import java.io.IOException; + +class FinalizeLogosEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/%s/finalizeLogos"; + + FinalizeLogosEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(FinalizeLogosRequest request) { + String path = String.format(PATH, request.themeId); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + return RequestBuilder.put(uri) + .setHeader("Content-Type", "application/json") + .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON)); + } + + @Override + public Void parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + return null; + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java b/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java new file mode 100644 index 000000000..72102d0ec --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.VonageUnexpectedException; +import java.util.List; +import java.util.UUID; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +class FinalizeLogosRequest { + @JsonProperty("keys") List keys; + @JsonIgnore UUID themeId; + + FinalizeLogosRequest(UUID themeId, List keys) { + this.keys = keys; + this.themeId = themeId; + } + + String toJson() { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(this); + } + catch (JsonProcessingException jpe) { + throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java b/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java new file mode 100644 index 000000000..b04a810b2 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +class GetLogoUploadUrlsEndpoint extends AbstractMethod> { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/logos-upload-urls"; + + GetLogoUploadUrlsEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(Void request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public List parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (json == null || json.isEmpty()) { + return Collections.emptyList(); + } + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, new TypeReference>() {}); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce List from json.", ex); + } + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java b/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java new file mode 100644 index 000000000..49e75313d --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.UUID; + +class GetRecordingEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/recordings/%s"; + + GetRecordingEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UUID recordingId) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, recordingId); + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public Recording parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return Recording.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java new file mode 100644 index 000000000..79cdaee27 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.UUID; + +class GetRoomEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/rooms/%s"; + + GetRoomEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UUID roomId) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, roomId); + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public MeetingRoom parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return MeetingRoom.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java new file mode 100644 index 000000000..8fca41931 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.UUID; + +class GetThemeEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/%s"; + + GetThemeEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UUID themeId) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, themeId); + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public Theme parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return Theme.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java b/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java new file mode 100644 index 000000000..1b5d92422 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class InitialJoinOptions { + private MicrophoneState microphoneState; + + protected InitialJoinOptions() { + } + + InitialJoinOptions(Builder builder) { + microphoneState = builder.microphoneState; + } + + /** + * The default microphone option for users in the pre-join screen of this room. + * + * @return The microphone state, as an enum + */ + @JsonProperty("microphone_state") + public MicrophoneState getMicrophoneState() { + return microphoneState; + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private MicrophoneState microphoneState; + + Builder() {} + + /** + * + * @param microphoneState Set the default microphone option for users in the pre-join screen of this room. + * + * @return This builder. + */ + public Builder microphoneState(MicrophoneState microphoneState) { + this.microphoneState = microphoneState; + return this; + } + + + /** + * Builds the {@linkplain InitialJoinOptions}. + * + * @return An instance of InitialJoinOptions, populated with all fields from this builder. + */ + public InitialJoinOptions build() { + return new InitialJoinOptions(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java b/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java new file mode 100644 index 000000000..fdc1ac2bc --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the level of approval needed to join the meeting in the room. + */ +public enum JoinApprovalLevel { + /** + * Participants can join the meeting at any time without approval. + */ + NONE, + + /** + * Participants will join the meeting only after the host joined. + */ + AFTER_OWNER_ONLY, + + /** + * Participants will join the waiting room and the host will deny/approve them. + */ + EXPLICIT_APPROVAL; + + @JsonCreator + public static JoinApprovalLevel fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java b/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java new file mode 100644 index 000000000..d1e907a6e --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.List; + +class ListDialNumbersEndpoint extends AbstractMethod> { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/dial-in-numbers"; + + ListDialNumbersEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(Void request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public List parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, new TypeReference>() {}); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java b/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java new file mode 100644 index 000000000..14a1bcdf4 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class ListRecordingsEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/sessions/%s/recordings"; + + ListRecordingsEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(String sessionId) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, sessionId); + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public ListRecordingsResponse parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (json == null || json.isEmpty()) { + return new ListRecordingsResponse(); + } + return ListRecordingsResponse.fromJson(json); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java b/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java new file mode 100644 index 000000000..de6aeb08d --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageResponseParseException; +import java.io.IOException; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +class ListRecordingsResponse { + @JsonProperty("_embedded") private Embedded embedded; + + static class Embedded { + @JsonProperty("recordings") List recordings; + } + + public List getRecordings() { + return embedded != null ? embedded.recordings : null; + } + + public static ListRecordingsResponse fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper.readValue(json, ListRecordingsResponse.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce ListRecordingsResponse from json.", ex); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java b/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java new file mode 100644 index 000000000..59e71d191 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class ListRoomsEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/rooms"; + + ListRoomsEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(ListRoomsRequest request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return request.addParameters(RequestBuilder.get(uri)) + .setHeader("Accept", "application/json"); + } + + @Override + public ListRoomsResponse parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return ListRoomsResponse.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java b/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java new file mode 100644 index 000000000..4188044be --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import org.apache.http.client.methods.RequestBuilder; +import java.util.UUID; + +class ListRoomsRequest { + final UUID themeId; + final Integer pageSize, startId, endId; + + ListRoomsRequest(Integer startId, Integer endId, Integer pageSize, UUID themeId) { + this.themeId = themeId; + this.startId = startId; + this.endId = endId; + this.pageSize = pageSize; + } + + RequestBuilder addParameters(RequestBuilder builder) { + if (startId != null) { + builder.addParameter("start_id", String.valueOf(startId)); + } + if (endId != null) { + builder.addParameter("end_id", String.valueOf(endId)); + } + if (pageSize != null) { + builder.addParameter("page_size", String.valueOf(pageSize)); + } + return builder; + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java b/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java new file mode 100644 index 000000000..0e7c1fb67 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.common.HalPageResponse; +import java.io.IOException; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +class ListRoomsResponse extends HalPageResponse { + @JsonProperty("_embedded") private List rooms; + + protected ListRoomsResponse() { + } + + /** + * The embedded response containing the list of meeting rooms. + * + * @return The list of rooms. + */ + public List getMeetingRooms() { + return rooms; + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with the fields populated, if present. + */ + public static ListRoomsResponse fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper.readValue(json, ListRoomsResponse.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce ListRoomsResponse from json.", ex); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java b/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java new file mode 100644 index 000000000..7100f98f0 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +class ListThemesEndpoint extends AbstractMethod> { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes"; + + ListThemesEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(Void request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.get(uri).setHeader("Accept", "application/json"); + } + + @Override + public List parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (json == null || json.isEmpty()) { + return Collections.emptyList(); + } + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, new TypeReference>() {}); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/LogoType.java b/src/main/java/com/vonage/client/meetings/LogoType.java new file mode 100644 index 000000000..3ea45812f --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/LogoType.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the type of logo returned in {@link LogoUploadsUrlResponse.Fields}. + */ +public enum LogoType { + WHITE, + COLORED, + FAVICON; + + @JsonCreator + public static LogoType fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java b/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java new file mode 100644 index 000000000..349bfc682 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.vonage.client.VonageResponseParseException; +import javax.activation.MimeType; +import javax.activation.MimeTypeParseException; +import java.net.URI; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class LogoUploadsUrlResponse { + private URI url; + private Fields fields; + + protected LogoUploadsUrlResponse() { + } + + /** + * Storage system URL. + * + * @return The URL. + */ + @JsonProperty("url") + public URI getUrl() { + return url; + } + + /** + * Fields property has to be part of the POST request's body. + * + * @return The field properties. + */ + @JsonProperty("fields") + public Fields getFields() { + return fields; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Fields { + private LogoType logoType; + private MimeType contentType; + private String key, bucket, policy, amzDate, amzAlgorithm, amzCredential, amzSecurityToken, amzSignature; + + protected Fields() { + } + + /** + * MIME type of the content. + * + * @return The image type. + */ + @JsonGetter("Content-Type") + public MimeType getContentType() { + return contentType; + } + + @JsonSetter("Content-Type") + protected void setContentType(String contentType) { + try { + this.contentType = contentType != null ? new MimeType(contentType) : null; + } + catch (MimeTypeParseException ex) { + throw new VonageResponseParseException("Invalid MIME type: "+contentType, ex); + } + } + + /** + * Logo's key in storage system. + * + * @return The logo key. + */ + @JsonProperty("key") + public String getKey() { + return key; + } + + /** + * Logo's type. + * + * @return The logo type, as an enum. + */ + @JsonProperty("logoType") + public LogoType getLogoType() { + return logoType; + } + + /** + * Bucket name to upload to. + * + * @return The bucket name. + */ + @JsonProperty("bucket") + public String getBucket() { + return bucket; + } + + @JsonProperty("X-Amz-Algorithm") + public String getAmzAlgorithm() { + return amzAlgorithm; + } + + @JsonProperty("X-Amz-Credential") + public String getAmzCredential() { + return amzCredential; + } + + @JsonProperty("X-Amz-Date") + public String getAmzDate() { + return amzDate; + } + + @JsonProperty("X-Amz-Security-Token") + public String getAmzSecurityToken() { + return amzSecurityToken; + } + + @JsonProperty("Policy") + public String getPolicy() { + return policy; + } + + @JsonProperty("X-Amz-Signature") + public String getAmzSignature() { + return amzSignature; + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/MeetingRoom.java b/src/main/java/com/vonage/client/meetings/MeetingRoom.java new file mode 100644 index 000000000..342df0d6c --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/MeetingRoom.java @@ -0,0 +1,488 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.VonageUnexpectedException; +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MeetingRoom { + private Instant createdAt, expiresAt; + private UUID id, themeId; + private String displayName, metadata, meetingCode; + private Boolean isAvailable, expireAfterUse; + private RoomType type; + private JoinApprovalLevel joinApprovalLevel; + private RecordingOptions recordingOptions; + private InitialJoinOptions initialJoinOptions; + private UISettings uiSettings; + private CallbackUrls callbackUrls; + private AvailableFeatures availableFeatures; + private RoomLinks links; + + protected MeetingRoom() { + } + + MeetingRoom(Builder builder) { + if ((displayName = builder.displayName) == null || displayName.trim().isEmpty()) { + throw new IllegalArgumentException("Display name is required and cannot be empty."); + } + validateExpiresAtAndRoomType(expiresAt = builder.expiresAt, type = builder.type); + if ((expireAfterUse = builder.expireAfterUse) != null && type == RoomType.INSTANT) { + throw new IllegalStateException("expireAfterUse is not applicable to "+type+" rooms."); + } + metadata = builder.metadata; + isAvailable = builder.isAvailable; + recordingOptions = builder.recordingOptions; + initialJoinOptions = builder.initialJoinOptions; + callbackUrls = builder.callbackUrls; + availableFeatures = builder.availableFeatures; + themeId = builder.themeId; + joinApprovalLevel = builder.joinApprovalLevel; + uiSettings = builder.uiSettings; + } + + static void validateExpiresAtAndRoomType(Instant expiresAt, RoomType type) { + if (type == RoomType.INSTANT && expiresAt != null) { + throw new IllegalStateException("Expiration time should not be specified for "+type+" rooms."); + } + else if (type == RoomType.LONG_TERM && expiresAt == null) { + throw new IllegalStateException("Expiration time must be specified for "+type+" rooms."); + } + if (expiresAt != null && expiresAt.isBefore(Instant.now().plusSeconds(600))) { + throw new IllegalArgumentException("Expiration time should be more than 10 minutes from now."); + } + } + + /** + * Name of the meeting room. + * + * @return The display name. + */ + @JsonProperty("display_name") + public String getDisplayName() { + return displayName; + } + + /** + * Free text that can be attached to a room. + * This will be passed in the form of a header in events related to this room. + * + * @return The room description / metadata. + */ + @JsonProperty("metadata") + public String getMetadata() { + return metadata; + } + + /** + * The meeting code, which is used in the URL to join the meeting. + * + * @return The share code for this meeting. + */ + @JsonProperty("meeting_code") + public String getMeetingCode() { + return meetingCode; + } + + /** + * Type of room. + * + * @return The room type, as an enum. + */ + @JsonProperty("type") + public RoomType getType() { + return type; + } + + /** + * Once a room becomes unavailable, no new sessions can be created under it. + * + * @return Whether the room is available, or {@code null} if unknown. + */ + @JsonProperty("is_available") + public Boolean getIsAvailable() { + return isAvailable; + } + + /** + * Close the room after a session ends. Only relevant for long_term rooms. + * + * @return {@code true} if the room will close after end of session, or {@code null} if unknown / not applicable. + */ + @JsonProperty("expire_after_use") + public Boolean getExpireAfterUse() { + return expireAfterUse; + } + + /** + * Options for recording. + * + * @return The recording options. + */ + @JsonProperty("recording_options") + public RecordingOptions getRecordingOptions() { + return recordingOptions; + } + + /** + * Options for when a participant joins a meeting. + * + * @return The initial joining options. + */ + @JsonProperty("initial_join_options") + public InitialJoinOptions getInitialJoinOptions() { + return initialJoinOptions; + } + + /** + * Settings for the user interface of this meeting. + * + * @return The UI settings object. + */ + @JsonProperty("ui_settings") + public UISettings getUiSettings() { + return uiSettings; + } + + /** + * The callback URLs for this meeting. + * + * @return The CallbackUrls object. + */ + @JsonProperty("callback_urls") + public CallbackUrls getCallbackUrls() { + return callbackUrls; + } + + /** + * The available features for this meeting. + * + * @return The AvailableFeatures object. + */ + @JsonProperty("available_features") + public AvailableFeatures getAvailableFeatures() { + return availableFeatures; + } + + /** + * ID of the theme for this room. + * + * @return The theme ID. + */ + @JsonProperty("theme_id") + public UUID getThemeId() { + return themeId; + } + + /** + * The level of approval needed to join the meeting in the room. + * + * @return The approval level, as an enum. + */ + @JsonProperty("join_approval_level") + public JoinApprovalLevel getJoinApprovalLevel() { + return joinApprovalLevel; + } + + /** + * The time for when the room was created, expressed in ISO 8601 format. + * + * @return The room creation time. + */ + @JsonProperty("created_at") + public Instant getCreatedAt() { + return createdAt; + } + + /** + * The time for when the room will be expired, expressed in ISO 8601 format. + * + * @return The room expiration time. + */ + @JsonProperty("expires_at") + public Instant getExpiresAt() { + return expiresAt; + } + + /** + * Identifier of the meeting room. + * + * @return The room ID. + */ + @JsonProperty("id") + public UUID getId() { + return id; + } + + /** + * Useful links. + * + * @return The nested links object. + */ + @JsonProperty("_links") + public RoomLinks getLinks() { + return links; + } + + /** + * Updates (hydrates) this object's fields from additional JSON data. + * + * @param json The JSON payload. + */ + public void updateFromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.readerForUpdating(this).readValue(json, getClass()); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to update "+getClass().getSimpleName()+" from json.", ex); + } + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with the fields populated, if present. + */ + public static MeetingRoom fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper.readValue(json, MeetingRoom.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce MeetingRoom from json.", ex); + } + } + + /** + * Generates a JSON payload from this request. + * + * @return JSON representation of this MeetingRoom object. + */ + public String toJson() { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + return mapper.writeValueAsString(this); + } + catch (JsonProcessingException jpe) { + throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe); + } + } + + /** + * Entry point for constructing an instance of this class. + * + * @param displayName Name of the meeting room. + * + * @return A new Builder. + */ + public static Builder builder(String displayName) { + return new Builder(displayName); + } + + public static class Builder { + private final String displayName; + private UUID themeId; + private String metadata; + private Instant expiresAt; + private Boolean isAvailable, expireAfterUse; + private RoomType type; + private JoinApprovalLevel joinApprovalLevel; + private RecordingOptions recordingOptions; + private InitialJoinOptions initialJoinOptions; + private UISettings uiSettings; + private CallbackUrls callbackUrls; + private AvailableFeatures availableFeatures; + + Builder(String displayName) { + this.displayName = displayName; + } + + /** + * Free text that can be attached to a room. This will be passed in the form of a header + * in events related to this room. + * + * @param metadata Additional text / data to be associated with this room. + * + * @return This builder. + */ + public Builder metadata(String metadata) { + this.metadata = metadata; + return this; + } + + /** + * Type of room. + * + * @param type The room type. + * + * @return This builder. + */ + public Builder type(RoomType type) { + this.type = type; + return this; + } + + /** + * Once a room becomes unavailable, no new sessions can be created under it. + * + * @param isAvailable Whether the room is available. + * + * @return This builder. + */ + public Builder isAvailable(boolean isAvailable) { + this.isAvailable = isAvailable; + return this; + } + + /** + * Close the room after a session ends. Only relevant for long term rooms. + * + * @param expireAfterUse Whether the room should close after use. + * + * @return This builder. + */ + public Builder expireAfterUse(boolean expireAfterUse) { + this.expireAfterUse = expireAfterUse; + return this; + } + + /** + * Options for recording. + * + * @param recordingOptions The recording options. + * + * @return This builder. + */ + public Builder recordingOptions(RecordingOptions recordingOptions) { + this.recordingOptions = recordingOptions; + return this; + } + + /** + * Options for when a participant joins a meeting. + * + * @param initialJoinOptions The initial join options for each participant. + * + * @return This builder. + */ + public Builder initialJoinOptions(InitialJoinOptions initialJoinOptions) { + this.initialJoinOptions = initialJoinOptions; + return this; + } + + /** + * Settings for the user interface of this meeting. + * + * @param uiSettings The user interface settings. + * + * @return This builder. + */ + public Builder uiSettings(UISettings uiSettings) { + this.uiSettings = uiSettings; + return this; + } + + /** + * Callback URLs for this meeting. + * + * @param callbackUrls The additional URLs for this meeting. + * + * @return This builder. + */ + public Builder callbackUrls(CallbackUrls callbackUrls) { + this.callbackUrls = callbackUrls; + return this; + } + + /** + * Available features for this meeting. + * + * @param availableFeatures The features available for this room. + * + * @return This builder. + */ + public Builder availableFeatures(AvailableFeatures availableFeatures) { + this.availableFeatures = availableFeatures; + return this; + } + + /** + * ID of the theme for this room. + * + * @param themeId Unique theme identifier. + * + * @return This builder. + */ + public Builder themeId(UUID themeId) { + this.themeId = themeId; + return this; + } + + /** + * Level of approval needed to join the meeting in the room. + * + * @param joinApprovalLevel The permission setting for joining this room. + * + * @return This builder. + */ + public Builder joinApprovalLevel(JoinApprovalLevel joinApprovalLevel) { + this.joinApprovalLevel = joinApprovalLevel; + return this; + } + + /** + * NOTE: This parameter is REQUIRED if the room type is {@link RoomType#LONG_TERM}, + * but should not be present if the room type is {@link RoomType#INSTANT}. + * + * @param expiresAt The time for when the room will expire, expressed in ISO 8601 format. + * + * @return This builder. + */ + public Builder expiresAt(Instant expiresAt) { + this.expiresAt = expiresAt.truncatedTo(ChronoUnit.MILLIS); + return this; + } + + + /** + * Builds the {@linkplain MeetingRoom}. + * + * @return An instance of MeetingRoom, populated with all fields from this builder. + */ + public MeetingRoom build() { + return new MeetingRoom(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/MeetingsClient.java b/src/main/java/com/vonage/client/meetings/MeetingsClient.java new file mode 100644 index 000000000..149561896 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/MeetingsClient.java @@ -0,0 +1,430 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.*; +import com.vonage.client.common.HalPageResponse; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.*; + +public class MeetingsClient { + HttpClient httpClient; + final ListRoomsEndpoint listRooms; + final GetRoomEndpoint getRoom; + final CreateRoomEndpoint createRoom; + final UpdateRoomEndpoint updateRoom; + final SearchThemeRoomsEndpoint searchThemeRooms; + final ListThemesEndpoint listThemes; + final GetThemeEndpoint getTheme; + final CreateThemeEndpoint createTheme; + final UpdateThemeEndpoint updateTheme; + final DeleteThemeEndpoint deleteTheme; + final ListRecordingsEndpoint listRecordings; + final GetRecordingEndpoint getRecording; + final DeleteRecordingEndpoint deleteRecording; + final ListDialNumbersEndpoint listDialNumbers; + final UpdateApplicationEndpoint updateApplication; + final FinalizeLogosEndpoint finalizeLogos; + final GetLogoUploadUrlsEndpoint getLogoUploadUrls; + + /** + * Constructor. + * + * @param httpWrapper (REQUIRED) shared HTTP wrapper object used for making REST calls. + */ + public MeetingsClient(HttpWrapper httpWrapper) { + httpClient = httpWrapper.getHttpClient(); + listRooms = new ListRoomsEndpoint(httpWrapper); + getRoom = new GetRoomEndpoint(httpWrapper); + createRoom = new CreateRoomEndpoint(httpWrapper); + updateRoom = new UpdateRoomEndpoint(httpWrapper); + searchThemeRooms = new SearchThemeRoomsEndpoint(httpWrapper); + listThemes = new ListThemesEndpoint(httpWrapper); + getTheme = new GetThemeEndpoint(httpWrapper); + createTheme = new CreateThemeEndpoint(httpWrapper); + updateTheme = new UpdateThemeEndpoint(httpWrapper); + deleteTheme = new DeleteThemeEndpoint(httpWrapper); + listRecordings = new ListRecordingsEndpoint(httpWrapper); + getRecording = new GetRecordingEndpoint(httpWrapper); + deleteRecording = new DeleteRecordingEndpoint(httpWrapper); + listDialNumbers = new ListDialNumbersEndpoint(httpWrapper); + updateApplication = new UpdateApplicationEndpoint(httpWrapper); + finalizeLogos = new FinalizeLogosEndpoint(httpWrapper); + getLogoUploadUrls = new GetLogoUploadUrlsEndpoint(httpWrapper); + } + + static UUID validateThemeId(UUID themeId) { + return Objects.requireNonNull(themeId, "Theme ID is required."); + } + + static UUID validateRoomId(UUID roomId) { + return Objects.requireNonNull(roomId, "Room ID is required."); + } + + static UUID validateRecordingId(UUID recordingId) { + return Objects.requireNonNull(recordingId, "Recording ID is required."); + } + + static String validateSessionId(String sessionId) { + if (sessionId == null || sessionId.trim().isEmpty()) { + throw new IllegalArgumentException("Session ID cannot be null or empty."); + } + return sessionId; + } + + private int parseNextFromHalResponse(HalPageResponse response) { + final URI nextUrl = response.getLinks().getNextUrl(); + return URLEncodedUtils.parse(nextUrl, Charset.defaultCharset()) + .stream().filter(nvp -> "start_id".equals(nvp.getName())) + .findFirst().map(nvp -> Integer.parseInt(nvp.getValue())) + .orElseThrow(() -> new VonageClientException("Couldn't navigate to next page: "+nextUrl)); + + } + + private List getAllRoomsFromResponseRecursively( + AbstractMethod endpoint, ListRoomsRequest initialRequest) { + + final int initialPageSize = initialRequest.pageSize != null ? initialRequest.pageSize : 1000; + ListRoomsRequest request = new ListRoomsRequest( + initialRequest.startId, initialRequest.endId, initialPageSize, initialRequest.themeId + ); + ListRoomsResponse response = endpoint.execute(request); + + if (response.getTotalItems() < initialPageSize) { + return response.getMeetingRooms(); + } + else { + List rooms = new ArrayList<>(response.getMeetingRooms()); + do { + request = new ListRoomsRequest( + parseNextFromHalResponse(response), null, initialPageSize, request.themeId + ); + response = endpoint.execute(request); + rooms.addAll(response.getMeetingRooms()); + } + while (response.getTotalItems() < initialPageSize); + return rooms; + } + } + + /** + * Get all listed rooms in the application. + * + * @return The list of all meeting rooms. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public List listRooms() { + return getAllRoomsFromResponseRecursively(listRooms, + new ListRoomsRequest(null, null, null, null) + ); + } + + /** + * Get details of an existing room. + * + * @param roomId ID of the room to retrieve. + * + * @return The meeting room associated with the ID. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public MeetingRoom getRoom(UUID roomId) { + return getRoom.execute(validateRoomId(roomId)); + } + + /** + * Create a new room. + * + * @param room Properties of the meeting room. + * + * @return Details of the created meeting room. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public MeetingRoom createRoom(MeetingRoom room) { + return createRoom.execute(Objects.requireNonNull(room, "Meeting room is required.")); + } + + /** + * Update an existing room. + * + * @param roomId ID of the meeting room to be updated. + * @param roomUpdate Properties of the meeting room to change. + * + * @return Details of the updated meeting room. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public MeetingRoom updateRoom(UUID roomId, UpdateRoomRequest roomUpdate) { + Objects.requireNonNull(roomUpdate, "Room update request properties is required."); + roomUpdate.roomId = validateRoomId(roomId); + return updateRoom.execute(roomUpdate); + } + + /** + * Get rooms that are associated with a theme ID. + * + * @param themeId The theme ID to filter by. + * + * @return The list of rooms which use the theme. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public List searchRoomsByTheme(UUID themeId) { + return getAllRoomsFromResponseRecursively(searchThemeRooms, + new ListRoomsRequest(null, null, null, validateThemeId(themeId)) + ); + } + + /** + * Get all application themes. + * + * @return The list of themes. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public List listThemes() { + return listThemes.execute(null); + } + + /** + * Retrieve details of a theme by ID. + * + * @param themeId The theme ID. + * + * @return The theme associated with the ID. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public Theme getTheme(UUID themeId) { + return getTheme.execute(validateThemeId(themeId)); + } + + /** + * Create a new theme. + * + * @param theme The partial theme properties. + * + * @return The full created theme details. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public Theme createTheme(Theme theme) { + Objects.requireNonNull(theme, "Theme creation properties are required."); + Objects.requireNonNull(theme.getBrandText(), "Brand text is required."); + Objects.requireNonNull(theme.getMainColor(), "Main color is required."); + return createTheme.execute(theme); + } + + /** + * Update an existing theme. + * + * @param themeId ID of the theme to update. + * @param theme The partial theme properties to update. + * + * @return The full updated theme details. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public Theme updateTheme(UUID themeId, Theme theme) { + Objects.requireNonNull(theme, "Theme update properties are required."); + theme.themeId = validateThemeId(themeId); + return updateTheme.execute(theme); + } + + /** + * Delete a theme by its ID. + * + * @param themeId ID of the theme to delete. + * @param force Whether to delete the theme even if theme is used by rooms or as application default theme. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public void deleteTheme(UUID themeId, boolean force) { + deleteTheme.execute(new DeleteThemeRequest(validateThemeId(themeId), force)); + } + + /** + * Get recordings of a meeting session. + * + * @param sessionId The session ID to filter recordings by. + * + * @return The list of recordings for the session. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public List listRecordings(String sessionId) { + ListRecordingsResponse response = listRecordings.execute(validateSessionId(sessionId)); + List recordings = response.getRecordings(); + return recordings != null ? recordings : Collections.emptyList(); + } + + /** + * Get details of a recording. + * + * @param recordingId ID of the recording to retrieve. + * + * @return The recording properties. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public Recording getRecording(UUID recordingId) { + return getRecording.execute(validateRecordingId(recordingId)); + } + + /** + * Delete a recording. + * + * @param recordingId ID of the recording to delete. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public void deleteRecording(UUID recordingId) { + deleteRecording.execute(validateRecordingId(recordingId)); + } + + /** + * Get numbers that can be used to dial into a meeting. + * + * @return The list of dial-in numbers, along with their country code. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public List listDialNumbers() { + return listDialNumbers.execute(null); + } + + /** + * Update an existing application. + * + * @param updateRequest Properties of the application to update. + * + * @return The updated application details. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public Application updateApplication(UpdateApplicationRequest updateRequest) { + return updateApplication.execute(Objects.requireNonNull( + updateRequest, "Application update properties are required.") + ); + } + + /** + * Change logos to be permanent for a given theme. + * + * @param themeId The theme ID containing the logos. + * @param keys List of temporary theme's logo keys to make permanent + */ + void finalizeLogos(UUID themeId, List keys) { + if (keys == null || keys.isEmpty()) { + throw new IllegalArgumentException("Logo keys are required."); + } + finalizeLogos.execute(new FinalizeLogosRequest(validateThemeId(themeId), keys)); + } + + /** + * Get URLs that can be used to upload logos for a theme via a POST. + * + * @return List of URLs and respective credentials / tokens needed for uploading logos to them. + */ + List listLogoUploadUrls() { + return getLogoUploadUrls.execute(null); + } + + /** + * Finds the appropriate response object from {@linkplain #listLogoUploadUrls()} for the given logo type. + * + * @param logoType The logo type to get details for. + * @return The URL and credential fields for uploading the logo. + */ + LogoUploadsUrlResponse getUploadDetailsForLogoType(LogoType logoType) { + return listLogoUploadUrls().stream() + .filter(r -> logoType.equals(r.getFields().getLogoType())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Logo type "+logoType+" is unavailable.")); + } + + /** + * Uploads a logo to the cloud so that it can be used in themes. + * + * @param logoFile Absolute path to the image. + * @param details Credentials, logo key and URL to facilitate the upload request. + */ + void uploadLogo(Path logoFile, LogoUploadsUrlResponse details) { + try { + LogoUploadsUrlResponse.Fields fields = details.getFields(); + HttpEntity entity = MultipartEntityBuilder.create() + .addTextBody("Content-Type", fields.getContentType().toString()) + .addTextBody("key", fields.getKey()) + .addTextBody("logoType", fields.getLogoType().toString()) + .addTextBody("bucket", fields.getBucket()) + .addTextBody("X-Amz-Algorithm", fields.getAmzAlgorithm()) + .addTextBody("X-Amz-Credential", fields.getAmzCredential()) + .addTextBody("X-Amz-Date", fields.getAmzDate()) + .addTextBody("X-Amz-Security-Token", fields.getAmzSecurityToken()) + .addTextBody("Policy", fields.getPolicy()) + .addTextBody("X-Amz-Signature", fields.getAmzSignature()) + .addBinaryBody("file", logoFile.toFile()) + .build(); + HttpResponse response = httpClient.execute( + RequestBuilder.post(details.getUrl()).setEntity(entity).build() + ); + StatusLine status = response.getStatusLine(); + int statusCode = status.getStatusCode(); + if (statusCode != 204) { + MeetingsResponseException mrx = new MeetingsResponseException( + "Logo upload failed ("+statusCode+"): "+status.getReasonPhrase() + ); + mrx.setStatusCode(statusCode); + throw mrx; + } + } + catch (IOException ex) { + throw new VonageUnexpectedException(ex); + } + } + + /** + * Upload a logo image and associates it with a theme. + * + * @param themeId ID of the theme which the logo will be associated with. + * @param logoType The logo type to upload. + * @param pngFile Absolute path to the logo image. For restrictions, refer to + * + * the documentation. Generally, the image must be a PNG under 1MB, square, under 300x300 pixels and + * have a transparent background. + * + * @throws MeetingsResponseException If there is an error encountered when processing the request. + */ + public void updateThemeLogo(UUID themeId, LogoType logoType, Path pngFile) { + LogoUploadsUrlResponse target = getUploadDetailsForLogoType( + Objects.requireNonNull(logoType, "Logo type cannot be null.") + ); + uploadLogo(Objects.requireNonNull(pngFile, "Image file cannot be null."), target); + finalizeLogos(themeId, Collections.singletonList(target.getFields().getKey())); + } +} diff --git a/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java b/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java new file mode 100644 index 000000000..02caa16a5 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java @@ -0,0 +1,211 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageResponseParseException; +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.util.UUID; + +/** + * Represents a deserialized callback response webhook for event updates from the Meetings API. + */ +public class MeetingsEventCallback { + private EventType event; + private String sessionId, participantName, participantType; + private UUID roomId, recordingId, participantId; + private RoomType roomType; + private Instant createdAt, startedAt, expiresAt, endedAt; + private Integer duration; + private URI recordingUrl; + private Boolean isHost; + + protected MeetingsEventCallback() { + } + + /** + * Type of event represented by this object. + * + * @return The event type as an enum. + */ + @JsonProperty("event") + public EventType getEvent() { + return event; + } + + /** + * Corresponds to the underlying Video API session ID. + * + * @return The session ID, or {@code null} if not applicable. + */ + @JsonProperty("session_id") + public String getSessionId() { + return sessionId; + } + + /** + * The participant name. + * + * @return Display name of the participant, or {@code null} if not applicable. + */ + @JsonProperty("name") + public String getParticipantName() { + return participantName; + } + + /** + * The participant type (e.g. "Guest"). + * + * @return Type of participant, or {@code null} if not applicable. + */ + @JsonProperty("type") + public String getParticipantType() { + return participantType; + } + + /** + * Meeting room's ID. + * + * @return The room ID, or {@code null} if not applicable. + */ + @JsonProperty("room_id") + public UUID getRoomId() { + return roomId; + } + + /** + * Unique recording ID for the meeting. + * + * @return The recording ID, or {@code null} if not applicable. + */ + @JsonProperty("recording_id") + public UUID getRecordingId() { + return recordingId; + } + + /** + * Unique identifier for the participant. + * + * @return The participant ID, or {@code null} if not applicable. + */ + @JsonProperty("participant_id") + public UUID getParticipantId() { + return participantId; + } + + /** + * The type of meeting room. + * + * @return The meeting room type as an enum, or {@code null} if not applicable. + */ + @JsonProperty("room_type") + public RoomType getRoomType() { + return roomType; + } + + /** + * The date-time when the room will expire, expressed in ISO 8601 format. + * + * @return The room expiration timestamp, or {@code null} if not applicable. + */ + @JsonProperty("expires_at") + public Instant getExpiresAt() { + return expiresAt; + } + + /** + * The date-time when the room was created, expressed in ISO 8601 format. + * + * @return The room creation timestamp, or {@code null} if not applicable. + */ + @JsonProperty("created_at") + public Instant getCreatedAt() { + return createdAt; + } + + /** + * The date-time when the session started, expressed in ISO 8601 format. + * + * @return , or {@code null} if not applicable. + */ + @JsonProperty("started_at") + public Instant getStartedAt() { + return startedAt; + } + + /** + * The date-time the session or recording ended, expressed in ISO 8601 format. + * + * @return The end timestamp, or {@code null} if not applicable. + */ + @JsonProperty("ended_at") + public Instant getEndedAt() { + return endedAt; + } + + /** + * Duration of the recording in seconds. + * + * @return The duration in seconds, or {@code null} if not applicable. + */ + @JsonProperty("duration") + public Integer getDuration() { + return duration; + } + + /** + * URL of the uploaded recording. + * + * @return The recording URL, or {@code null} if not applicable. + */ + @JsonProperty("url") + public URI getRecordingUrl() { + return recordingUrl; + } + + /** + * Indicates if this participant is the session's host. + * + * @return Whether the participant is the session host, or {@code null} if not applicable. + */ + @JsonProperty("is_host") + public Boolean getIsHost() { + return isHost; + } + + /** + * Constructs an instance of this class from a JSON payload. + * + * @param json The webhook response JSON string. + * + * @return The deserialized webhook response object. + * @throws VonageResponseParseException If the response could not be deserialized. + */ + public static MeetingsEventCallback fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper.readValue(json, MeetingsEventCallback.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce MeetingsEventCallback from json.", ex); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java b/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java new file mode 100644 index 000000000..c59a752b2 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.*; +import com.vonage.client.VonageApiResponseException; +import org.apache.http.HttpResponse; +import java.io.IOException; + +/** + * Response returned when an error is encountered (i.e. the API returns a non-2xx status code). + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class MeetingsResponseException extends VonageApiResponseException { + @JsonProperty("status") private Integer status; + @JsonProperty("name") private String name; + @JsonProperty("message") private String message; + + public MeetingsResponseException() { + super(); + } + + MeetingsResponseException(String message) { + super(message); + } + + void setStatusCode(Integer status) { + this.status = statusCode = status; + } + + public String getName() { + return name; + } + + @JsonGetter("message") + @Override + public String getMessage() { + return message != null ? message : super.getMessage(); + } + + private static MeetingsResponseException setStatus(MeetingsResponseException ex) { + if (ex.status == null || ex.status < 100) { + ex.setStatusCode(ex.statusCode); + } + else if (ex.statusCode < 100) { + ex.setStatusCode(ex.status); + } + return ex; + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with all known fields populated from the JSON payload, if present. + */ + @JsonCreator + public static MeetingsResponseException fromJson(String json) { + return setStatus(fromJson(MeetingsResponseException.class, json)); + } + + static MeetingsResponseException fromHttpResponse(HttpResponse response) throws IOException { + return setStatus(fromHttpResponse(MeetingsResponseException.class, response)); + } +} diff --git a/src/main/java/com/vonage/client/meetings/MicrophoneState.java b/src/main/java/com/vonage/client/meetings/MicrophoneState.java new file mode 100644 index 000000000..6d5bf4821 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/MicrophoneState.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Set the default microphone option for users in the pre-join screen of this room. + */ +public enum MicrophoneState { + ON, + OFF, + DEFAULT; + + @JsonCreator + public static MicrophoneState fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/Recording.java b/src/main/java/com/vonage/client/meetings/Recording.java new file mode 100644 index 000000000..7fdc26ac3 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/Recording.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageResponseParseException; +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.util.UUID; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Recording { + private UUID id; + private String sessionId; + private Instant startedAt, endedAt; + private RecordingStatus status; + @JsonProperty("_links") private RecordingLinks links; + + protected Recording() { + } + + /** + * Identifier of the recording. + * + * @return The recording ID. + */ + @JsonProperty("id") + public UUID getId() { + return id; + } + + /** + * Corresponds to the underlying Video API session ID. + * + * @return The video session ID. + */ + @JsonProperty("session_id") + public String getSessionId() { + return sessionId; + } + + /** + * Recording start time, in ISO 8601 format. + * + * @return The recording start time. + */ + @JsonProperty("started_at") + public Instant getStartedAt() { + return startedAt; + } + + /** + * Recording end time, in ISO 8601 format. + * + * @return The recording end time. + */ + @JsonProperty("ended_at") + public Instant getEndedAt() { + return endedAt; + } + + /** + * Status of the recording. + * + * @return The recording status, as an enum. + */ + @JsonProperty("status") + public RecordingStatus getStatus() { + return status; + } + + /** + * The URL property of the {@code _links} response. + * + * @return The recording URL or {@code null} if not available. + */ + @JsonIgnore + public URI getUrl() { + return links != null ? links.getUrl() : null; + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with the fields populated, if present. + */ + public static Recording fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper.readValue(json, Recording.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce Recording from json.", ex); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/RecordingLinks.java b/src/main/java/com/vonage/client/meetings/RecordingLinks.java new file mode 100644 index 000000000..3f38c6d8c --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RecordingLinks.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; + +@JsonIgnoreProperties(ignoreUnknown = true) +class RecordingLinks { + private UrlContainer url; + + /** + * Recording URL. + * + * @return The recording URL. + */ + @JsonProperty("url") + public URI getUrl() { + return url != null ? url.getHref() : null; + } +} diff --git a/src/main/java/com/vonage/client/meetings/RecordingOptions.java b/src/main/java/com/vonage/client/meetings/RecordingOptions.java new file mode 100644 index 000000000..169252d44 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RecordingOptions.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RecordingOptions { + private Boolean autoRecord, recordOnlyOwner; + + protected RecordingOptions() { + } + + RecordingOptions(Builder builder) { + autoRecord = builder.autoRecord; + recordOnlyOwner = builder.recordOnlyOwner; + } + + /** + * Automatically record all sessions in this room. Recording cannot be stopped when this is set to true. + * + * @return Whether all sessions are automatically recorded, or {@code null} if unknown. + */ + @JsonProperty("auto_record") + public Boolean getAutoRecord() { + return autoRecord; + } + + /** + * Record only the owner screen or any share screen of the video. + * + * @return Whether only the owner screen is recorded, or {@code null} if unknown. + */ + @JsonProperty("record_only_owner") + public Boolean getRecordOnlyOwner() { + return recordOnlyOwner; + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Boolean autoRecord, recordOnlyOwner; + + Builder() {} + + /** + * + * @param autoRecord Automatically record all sessions in this room. Recording cannot be stopped when this is set to true. + * + * @return This builder. + */ + public Builder autoRecord(boolean autoRecord) { + this.autoRecord = autoRecord; + return this; + } + + /** + * + * @param recordOnlyOwner Record only the owner screen or any share screen of the video. + * + * @return This builder. + */ + public Builder recordOnlyOwner(boolean recordOnlyOwner) { + this.recordOnlyOwner = recordOnlyOwner; + return this; + } + + + /** + * Builds the {@linkplain RecordingOptions}. + * + * @return An instance of RecordingOptions, populated with all fields from this builder. + */ + public RecordingOptions build() { + return new RecordingOptions(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/RecordingStatus.java b/src/main/java/com/vonage/client/meetings/RecordingStatus.java new file mode 100644 index 000000000..ab3df9f04 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RecordingStatus.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the recording state of a meeting. + */ +public enum RecordingStatus { + STARTED, + STOPPED, + PAUSED, + UPLOADED; + + @JsonCreator + public static RecordingStatus fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/RoomLanguage.java b/src/main/java/com/vonage/client/meetings/RoomLanguage.java new file mode 100644 index 000000000..af03fe8a5 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RoomLanguage.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the language in {@link UISettings}. Default is English. + */ +public enum RoomLanguage { + /** + * English + */ + EN, + + /** + * Hebrew + */ + HE, + + /** + * Spanish + */ + ES, + + /** + * Portuguese + */ + PT, + + /** + * Italian + */ + IT, + + /** + * Catalan + */ + CA, + + /** + * French + */ + FR, + + /** + * German + */ + DE; + + @JsonCreator + public static RoomLanguage fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/RoomLinks.java b/src/main/java/com/vonage/client/meetings/RoomLinks.java new file mode 100644 index 000000000..6711f5074 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RoomLinks.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class RoomLinks { + @JsonProperty("host_url") UrlContainer hostUrl; + @JsonProperty("guest_url") UrlContainer guestUrl; + + protected RoomLinks() { + } + + /** + * @return The host URL. + */ + public URI getHostUrl() { + return hostUrl != null ? hostUrl.getHref() : null; + } + + /** + * @return The guest URL. + */ + public URI getGuestUrl() { + return guestUrl != null ? guestUrl.getHref() : null; + } +} diff --git a/src/main/java/com/vonage/client/meetings/RoomType.java b/src/main/java/com/vonage/client/meetings/RoomType.java new file mode 100644 index 000000000..ae727a6fd --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/RoomType.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the type of meeting room. + */ +public enum RoomType { + /** + * "Instant" is active for 10 minutes until the first participant joins the room + * and remains active for 10 minutes after the last participant leaves. + */ + INSTANT, + + /** + * "Long term" expires after a specific date-time. + */ + LONG_TERM; + + @JsonCreator + public static RoomType fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java b/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java new file mode 100644 index 000000000..bd729b39e --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class SearchThemeRoomsEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/%s/rooms"; + + SearchThemeRoomsEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(ListRoomsRequest request) { + String path = String.format(PATH, request.themeId); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + return request.addParameters(RequestBuilder.get(uri)) + .setHeader("Accept", "application/json"); + } + + @Override + public ListRoomsResponse parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return ListRoomsResponse.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/Theme.java b/src/main/java/com/vonage/client/meetings/Theme.java new file mode 100644 index 000000000..0d4a4bbab --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/Theme.java @@ -0,0 +1,336 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.VonageUnexpectedException; +import java.io.IOException; +import java.net.URI; +import java.util.UUID; +import java.util.regex.Pattern; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true, value = "theme_id", allowSetters = true) +public class Theme { + static final Pattern COLOR_PATTERN = Pattern.compile("(#[a-fA-F0-9]{6}|[a-fA-F0-9]{3})"); + + UUID themeId; + private UUID applicationId; + private ThemeDomain domain; + private String themeName, mainColor, accountId, shortCompanyUrl, + brandText, brandImageColored, brandImageWhite, brandedFavicon; + private URI brandImageColoredUrl, brandImageWhiteUrl, brandedFaviconUrl; + + protected Theme() { + } + + Theme(Builder builder) { + if ((mainColor = builder.mainColor) != null && !COLOR_PATTERN.matcher(mainColor).matches()) { + throw new IllegalArgumentException("Main color must be a valid hex pallet."); + } + + if ((brandText = builder.brandText) != null) { + if (brandText.length() > 200) { + throw new IllegalArgumentException("Brand text cannot exceed 200 characters."); + } + else if (brandText.trim().isEmpty()) { + throw new IllegalArgumentException("Brand text cannot be blank."); + } + } + + if ((themeName = builder.themeName) != null) { + if (themeName.length() > 200) { + throw new IllegalArgumentException("Theme name cannot exceed 200 characters."); + } + else if (themeName.trim().isEmpty()) { + throw new IllegalArgumentException("Theme name cannot be blank."); + } + } + + if ((shortCompanyUrl = builder.shortCompanyUrl) != null) { + if (shortCompanyUrl.length() > 128) { + throw new IllegalArgumentException("Short company URL cannot exceed 128 characters."); + } + else if (shortCompanyUrl.trim().isEmpty()) { + throw new IllegalArgumentException("Short company URL cannot be blank."); + } + } + } + + /** + * Unique theme ID. + * + * @return The theme ID. + */ + @JsonProperty("theme_id") + public UUID getThemeId() { + return themeId; + } + + /** + * Theme name. + * + * @return Name of the theme. + */ + @JsonProperty("theme_name") + public String getThemeName() { + return themeName; + } + + /** + * The theme's application domain. + * + * @return The domain as an enum. + */ + @JsonProperty("domain") + public ThemeDomain getDomain() { + return domain; + } + + /** + * Account ID. + * + * @return The account ID (API key). + */ + @JsonProperty("account_id") + public String getAccountId() { + return accountId; + } + + /** + * Unique application ID. + * + * @return The application ID. + */ + @JsonProperty("application_id") + public UUID getApplicationId() { + return applicationId; + } + + /** + * Main colour hex code. + * + * @return The main theme colour (hex value). + */ + @JsonProperty("main_color") + public String getMainColor() { + return mainColor; + } + + /** + * Company name to include in the URL. + * + * @return The company's short URL. + */ + @JsonProperty("short_company_url") + public String getShortCompanyUrl() { + return shortCompanyUrl; + } + + /** + * Brand text that will appear in the application. + * + * @return The branding text. + */ + @JsonProperty("brand_text") + public String getBrandText() { + return brandText; + } + + /** + * Coloured logo's key in storage system. + * + * @return The brand image in colour. + */ + @JsonProperty("brand_image_colored") + public String getBrandImageColored() { + return brandImageColored; + } + + /** + * White logo's key in storage system. + * + * @return The brand image in white. + */ + @JsonProperty("brand_image_white") + public String getBrandImageWhite() { + return brandImageWhite; + } + + /** + * Favicon's key in storage system. + * + * @return The brand favicon key. + */ + @JsonProperty("branded_favicon") + public String getBrandedFavicon() { + return brandedFavicon; + } + + /** + * Coloured logo's link. + * + * @return The coloured brand image URL. + */ + @JsonProperty("brand_image_colored_url") + public URI getBrandImageColoredUrl() { + return brandImageColoredUrl; + } + + /** + * White logo's link. + * + * @return The white brand image URL. + */ + @JsonProperty("brand_image_white_url") + public URI getBrandImageWhiteUrl() { + return brandImageWhiteUrl; + } + + /** + * Favicon's link. + * + * @return The favicon URL. + */ + @JsonProperty("branded_favicon_url") + public URI getBrandedFaviconUrl() { + return brandedFaviconUrl; + } + + /** + * Updates (hydrates) this object's fields from additional JSON data. + * + * @param json The JSON payload. + */ + public void updateFromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.readerForUpdating(this).readValue(json, getClass()); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to update "+getClass().getSimpleName()+" from json.", ex); + } + } + + /** + * Creates an instance of this class from a JSON payload. + * + * @param json The JSON string to parse. + * @return An instance of this class with the fields populated, if present. + */ + public static Theme fromJson(String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, Theme.class); + } + catch (IOException ex) { + throw new VonageResponseParseException("Failed to produce Theme from json.", ex); + } + } + + /** + * Generates a JSON payload from this request. + * + * @return JSON representation of this Theme object. + */ + public String toJson() { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(this); + } + catch (JsonProcessingException jpe) { + throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe); + } + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String themeName, mainColor, brandText, shortCompanyUrl; + + Builder() {} + + /** + * (OPTIONAL) The name to give the theme (maximum 200 characters). + * + * @param themeName The theme name. + * + * @return This builder. + */ + public Builder themeName(String themeName) { + this.themeName = themeName; + return this; + } + + /** + * (REQUIRED) The main theme colour code (hex value). + * + * @param mainColor The colour's hex code, including the # character. + * + * @return This builder. + */ + public Builder mainColor(String mainColor) { + this.mainColor = mainColor; + return this; + } + + /** + * (OPTIONAL) The short company URL. Must be a valid URI, cannot exceed 128 characters. + * + * @param shortCompanyUrl The URL as a String. + * + * @return This builder. + */ + public Builder shortCompanyUrl(String shortCompanyUrl) { + this.shortCompanyUrl = shortCompanyUrl; + return this; + } + + /** + * (REQUIRED) The brand / logo text. + * + * @param brandText The text. + * + * @return This builder. + */ + public Builder brandText(String brandText) { + this.brandText = brandText; + return this; + } + + + /** + * Builds the {@linkplain Theme}. + * + * @return An instance of Theme, populated with all fields from this builder. + */ + public Theme build() { + return new Theme(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/ThemeDomain.java b/src/main/java/com/vonage/client/meetings/ThemeDomain.java new file mode 100644 index 000000000..cdae3198c --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/ThemeDomain.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonCreator; + +/** + * Represents the application domain for a theme. + */ +public enum ThemeDomain { + VCP, + VBC; + + @JsonCreator + public static ThemeDomain fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException ex) { + return null; + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UISettings.java b/src/main/java/com/vonage/client/meetings/UISettings.java new file mode 100644 index 000000000..2c1efca17 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UISettings.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class UISettings { + private RoomLanguage language; + + UISettings(Builder builder) { + language = builder.language; + } + + protected UISettings() { + } + + /** + * Language setting. + * + * @return The language, as an enum. + */ + @JsonProperty("language") + public RoomLanguage getLanguage() { + return language; + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private RoomLanguage language; + + Builder() {} + + /** + * + * @param language The language setting. + * + * @return This builder. + */ + public Builder language(RoomLanguage language) { + this.language = language; + return this; + } + + /** + * Builds the {@linkplain UISettings}. + * + * @return An instance of UISettings, populated with all fields from this builder. + */ + public UISettings build() { + return new UISettings(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java new file mode 100644 index 000000000..72bc79cf1 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class UpdateApplicationEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/applications"; + + UpdateApplicationEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UpdateApplicationRequest request) { + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH; + return RequestBuilder.patch(uri) + .setHeader("Content-Type", "application/json") + .setHeader("Accept", "application/json") + .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON)); + } + + @Override + public Application parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return Application.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java b/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java new file mode 100644 index 000000000..9ebb89511 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.VonageUnexpectedException; +import java.util.Objects; +import java.util.UUID; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +public class UpdateApplicationRequest { + private final UUID defaultThemeId; + + UpdateApplicationRequest(Builder builder) { + defaultThemeId = Objects.requireNonNull(builder.defaultThemeId, "Default theme ID is required."); + } + + /** + * @return The theme ID to set as application default theme. + */ + @JsonProperty("default_theme_id") + public UUID getDefaultThemeId() { + return defaultThemeId; + } + + /** + * Generates a JSON payload from this request. + * + * @return JSON representation of this UpdateApplicationRequest object. + */ + public String toJson() { + try { + ObjectMapper mapper = new ObjectMapper(); + String nested = mapper.writeValueAsString(this); + return "{\"update_details\": "+nested+"}"; + } + catch (JsonProcessingException jpe) { + throw new VonageUnexpectedException( + "Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe + ); + } + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private UUID defaultThemeId; + + Builder() {} + + /** + * + * @param defaultThemeId The theme ID to set as application default theme. + * + * @return This builder. + */ + public Builder defaultThemeId(UUID defaultThemeId) { + this.defaultThemeId = defaultThemeId; + return this; + } + + + /** + * Builds the {@linkplain UpdateApplicationRequest}. + * + * @return An instance of UpdateApplicationRequest, populated with all fields from this builder. + */ + public UpdateApplicationRequest build() { + return new UpdateApplicationRequest(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java new file mode 100644 index 000000000..f64fb1252 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class UpdateRoomEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/rooms/%s"; + + UpdateRoomEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(UpdateRoomRequest request) { + String path = String.format(PATH, request.roomId); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + return RequestBuilder.patch(uri) + .setHeader("Content-Type", "application/json") + .setHeader("Accept", "application/json") + .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON)); + } + + @Override + public MeetingRoom parseResponse(HttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return MeetingRoom.fromJson(basicResponseHandler.handleResponse(response)); + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java b/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java new file mode 100644 index 000000000..78aee8acf --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java @@ -0,0 +1,245 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vonage.client.VonageUnexpectedException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +public class UpdateRoomRequest { + @JsonIgnore UUID roomId; + + private final Boolean expireAfterUse; + private final InitialJoinOptions initialJoinOptions; + private final CallbackUrls callbackUrls; + private final AvailableFeatures availableFeatures; + private final JoinApprovalLevel joinApprovalLevel; + private final UUID themeId; + @JsonIgnore private final Instant expiresAt; + + UpdateRoomRequest(Builder builder) { + expireAfterUse = builder.expireAfterUse; + initialJoinOptions = builder.initialJoinOptions; + callbackUrls = builder.callbackUrls; + availableFeatures = builder.availableFeatures; + joinApprovalLevel = builder.joinApprovalLevel; + themeId = builder.themeId; + MeetingRoom.validateExpiresAtAndRoomType(expiresAt = builder.expiresAt, null); + } + + /** + * Close the room after a session ends. Only relevant for long_term rooms. + * + * @return {@code true} if the room will close after end of session, or {@code null} if unknown / not applicable. + */ + @JsonProperty("expire_after_use") + public Boolean getExpireAfterUse() { + return expireAfterUse; + } + + /** + * Options for when a participant joins a meeting. + * + * @return The initial joining options. + */ + @JsonProperty("initial_join_options") + public InitialJoinOptions getInitialJoinOptions() { + return initialJoinOptions; + } + + /** + * The callback URLs for this meeting. + * + * @return The CallbackUrls object. + */ + @JsonProperty("callback_urls") + public CallbackUrls getCallbackUrls() { + return callbackUrls; + } + + /** + * The available features for this meeting. + * + * @return The AvailableFeatures object. + */ + @JsonProperty("available_features") + public AvailableFeatures getAvailableFeatures() { + return availableFeatures; + } + + /** + * ID of the theme for this room. + * + * @return The theme ID. + */ + @JsonProperty("theme_id") + public UUID getThemeId() { + return themeId; + } + + /** + * The level of approval needed to join the meeting in the room. + * + * @return The approval level, as an enum. + */ + @JsonProperty("join_approval_level") + public JoinApprovalLevel getJoinApprovalLevel() { + return joinApprovalLevel; + } + + /** + * The time for when the room will be expired, expressed in ISO 8601 format. + * + * @return The room expiration time. + */ + @JsonProperty("expires_at") + public Instant getExpiresAt() { + return expiresAt; + } + + /** + * Generates a JSON payload from this request. + * + * @return JSON representation of this UpdateRoomRequest object. + */ + public String toJson() { + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + return "{\"update_details\":"+mapper.writeValueAsString(this)+"}"; + } + catch (JsonProcessingException jpe) { + throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe); + } + } + + /** + * Entry point for constructing an instance of this class. + * + * @return A new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Boolean expireAfterUse; + private InitialJoinOptions initialJoinOptions; + private CallbackUrls callbackUrls; + private AvailableFeatures availableFeatures; + private JoinApprovalLevel joinApprovalLevel; + private UUID themeId; + private Instant expiresAt; + + Builder() {} + + /** + * + * @param expireAfterUse Close the room after a session ends. Only relevant for long_term rooms. + * + * @return This builder. + */ + public Builder expireAfterUse(Boolean expireAfterUse) { + this.expireAfterUse = expireAfterUse; + return this; + } + + /** + * + * @param initialJoinOptions Options for when a participant joins a meeting. + * + * @return This builder. + */ + public Builder initialJoinOptions(InitialJoinOptions initialJoinOptions) { + this.initialJoinOptions = initialJoinOptions; + return this; + } + + /** + * + * @param callbackUrls The callback URLs for this meeting. + * + * @return This builder. + */ + public Builder callbackUrls(CallbackUrls callbackUrls) { + this.callbackUrls = callbackUrls; + return this; + } + + /** + * + * @param availableFeatures The available features for this meeting. + * + * @return This builder. + */ + public Builder availableFeatures(AvailableFeatures availableFeatures) { + this.availableFeatures = availableFeatures; + return this; + } + + /** + * + * @param themeId ID of the theme for this room. + * + * @return This builder. + */ + public Builder themeId(UUID themeId) { + this.themeId = themeId; + return this; + } + + /** + * + * @param joinApprovalLevel The level of approval needed to join the meeting in the room. + * + * @return This builder. + */ + public Builder joinApprovalLevel(JoinApprovalLevel joinApprovalLevel) { + this.joinApprovalLevel = joinApprovalLevel; + return this; + } + + /** + * + * @param expiresAt The time for when the room will be expired, expressed in ISO 8601 format. + * + * @return This builder. + */ + public Builder expiresAt(Instant expiresAt) { + this.expiresAt = expiresAt.truncatedTo(ChronoUnit.MILLIS); + return this; + } + + /** + * Builds the {@linkplain UpdateRoomRequest}. + * + * @return An instance of UpdateRoomRequest, populated with all fields from this builder. + */ + public UpdateRoomRequest build() { + return new UpdateRoomRequest(this); + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java new file mode 100644 index 000000000..207e37a09 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.AbstractMethod; +import com.vonage.client.HttpWrapper; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.client.methods.RequestBuilder; +import java.io.IOException; + +class UpdateThemeEndpoint extends AbstractMethod { + private static final Class[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class}; + private static final String PATH = "/meetings/themes/%s"; + private Theme cachedTheme; + + UpdateThemeEndpoint(HttpWrapper httpWrapper) { + super(httpWrapper); + } + + @Override + protected Class[] getAcceptableAuthMethods() { + return ALLOWED_AUTH_METHODS; + } + + @Override + public RequestBuilder makeRequest(Theme request) { + String path = String.format(PATH, request.getThemeId()); + String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path; + String json = "{\"update_details\":" + (cachedTheme = request).toJson() + "}"; + return RequestBuilder.patch(uri) + .setHeader("Content-Type", "application/json") + .setHeader("Accept", "application/json") + .setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); + } + + @Override + public Theme parseResponse(HttpResponse response) throws IOException { + try { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + String json = basicResponseHandler.handleResponse(response); + if (cachedTheme != null) { + cachedTheme.updateFromJson(json); + return cachedTheme; + } + else { + return Theme.fromJson(json); + } + } + else { + throw MeetingsResponseException.fromHttpResponse(response); + } + } + finally { + cachedTheme = null; + } + } +} diff --git a/src/main/java/com/vonage/client/meetings/UrlContainer.java b/src/main/java/com/vonage/client/meetings/UrlContainer.java new file mode 100644 index 000000000..7160033e5 --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/UrlContainer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class UrlContainer { + URI href; + + protected UrlContainer() { + } + + /** + * @return The URL. + */ + @JsonProperty("href") + public URI getHref() { + return href; + } +} diff --git a/src/main/java/com/vonage/client/meetings/package-info.java b/src/main/java/com/vonage/client/meetings/package-info.java new file mode 100644 index 000000000..e4c4b33be --- /dev/null +++ b/src/main/java/com/vonage/client/meetings/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Vonage + * + * 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. + */ + +/** + * This package contains classes and sub-packages to support usage of the + * Meetings API. + *
+ * Please refer to + * the developer documentation for an overview of the concepts. + * + * @since 7.6.0 + */ +package com.vonage.client.meetings; \ No newline at end of file diff --git a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java index 072b29d24..e86681971 100644 --- a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java +++ b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java @@ -296,7 +296,7 @@ public void deleteListItem(UUID listId, UUID itemId) { * * @param listId Unique ID of the list. * - * @return The list items CSV file contents as a String. + * @return The list items contents as a CSV-formatted String. * @see #downloadListItems(UUID, Path) * * @throws ProactiveConnectResponseException If the list does not exist or couldn't be retrieved. @@ -309,7 +309,7 @@ public String downloadListItems(UUID listId) { /** * Download all items in a list in CSV format. - * Use {@link #downloadListItems(UUID)} to get the results as a raw binary. + * Use {@link #downloadListItems(UUID)} to get the results as a String. * * @param listId Unique ID of the list. * @param file Path of the file to write the downloaded results to. diff --git a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java index 469b921fc..22b9cd01e 100644 --- a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java +++ b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java @@ -18,11 +18,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import com.vonage.client.VonageApiResponseException; import org.apache.http.HttpResponse; import java.io.IOException; -import java.util.List; /** * Response returned when an error is encountered (i.e. the API returns a non-2xx status code). @@ -30,23 +28,11 @@ @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public final class ProactiveConnectResponseException extends VonageApiResponseException { - List errors; void setStatusCode(int statusCode) { this.statusCode = statusCode; } - /** - * Additional description of problems encountered with the request. - * This is typically only applicable to 400 or 409 error codes. - * - * @return The list of errors returned from the server (could be a Map or String), or {@code null} if none. - */ - @JsonProperty("errors") - public List getErrors() { - return errors; - } - /** * Creates an instance of this class from a JSON payload. * diff --git a/src/main/java/com/vonage/client/proactiveconnect/package-info.java b/src/main/java/com/vonage/client/proactiveconnect/package-info.java index 671cad0a9..da4466564 100644 --- a/src/main/java/com/vonage/client/proactiveconnect/package-info.java +++ b/src/main/java/com/vonage/client/proactiveconnect/package-info.java @@ -17,7 +17,7 @@ /** * This package contains classes and sub-packages to support usage of the * Vonage Proactive Connect API. - *

+ *
* * @since 7.6.0 */ diff --git a/src/main/java/com/vonage/client/verify2/VerificationCallback.java b/src/main/java/com/vonage/client/verify2/VerificationCallback.java index 2accbcfa4..a9adff0ba 100644 --- a/src/main/java/com/vonage/client/verify2/VerificationCallback.java +++ b/src/main/java/com/vonage/client/verify2/VerificationCallback.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.vonage.client.VonageResponseParseException; -import com.vonage.client.VonageUnexpectedException; import java.io.IOException; import java.net.URI; import java.time.Instant; @@ -179,7 +178,7 @@ public URI getSilentAuthUrl() { * @param json The webhook response JSON string. * * @return The deserialized webhook response object. - * @throws VonageUnexpectedException If the response could not be deserialized. + * @throws VonageResponseParseException If the response could not be deserialized. */ public static VerificationCallback fromJson(String json) { try { diff --git a/src/test/java/com/vonage/client/BugRepro.java b/src/test/java/com/vonage/client/BugRepro.java index 15bd56ab1..1d0c36512 100644 --- a/src/test/java/com/vonage/client/BugRepro.java +++ b/src/test/java/com/vonage/client/BugRepro.java @@ -25,22 +25,21 @@ * Convenience class for debugging / live testing. */ public class BugRepro { - static final String - VONAGE_API_KEY = System.getenv("VONAGE_API_KEY"), - VONAGE_API_SECRET = System.getenv("VONAGE_API_SECRET"), - VONAGE_APPLICATION_ID = System.getenv("VONAGE_APPLICATION_ID"), - VONAGE_PRIVATE_KEY_PATH = System.getenv("VONAGE_PRIVATE_KEY_PATH"); - public static void main(String[] args) throws Throwable { VonageClient client = VonageClient.builder() - .apiKey(VONAGE_API_KEY) - .apiSecret(VONAGE_API_SECRET) - .privateKeyPath(VONAGE_PRIVATE_KEY_PATH) - .applicationId(VONAGE_APPLICATION_ID) + .apiKey(System.getenv("VONAGE_API_KEY")) + .apiSecret(System.getenv("VONAGE_API_SECRET")) + .applicationId(System.getenv("VONAGE_APPLICATION_ID")) + .privateKeyPath(System.getenv("VONAGE_PRIVATE_KEY_PATH")) .build(); - // Debug code here + try { + // Debug code here - System.out.println("Success"); + System.out.println("Success"); + } + catch (Exception ex) { + ex.printStackTrace(); + } } } diff --git a/src/test/java/com/vonage/client/ClientTest.java b/src/test/java/com/vonage/client/ClientTest.java index 801fa2e0d..2c51e1d63 100644 --- a/src/test/java/com/vonage/client/ClientTest.java +++ b/src/test/java/com/vonage/client/ClientTest.java @@ -23,12 +23,15 @@ import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpUriRequest; -import static org.mockito.Mockito.*; import static org.junit.Assert.assertThrows; import org.junit.function.ThrowingRunnable; +import static org.mockito.Mockito.*; import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.UUID; +import java.util.function.Function; import java.util.function.Supplier; public abstract class ClientTest { @@ -48,7 +51,7 @@ protected HttpClient stubHttpClient(int statusCode) throws Exception { return stubHttpClient(statusCode, ""); } - protected HttpClient stubHttpClient(int statusCode, String content) throws Exception { + protected HttpClient stubHttpClient(int statusCode, String content, String... additionalReturns) throws Exception { HttpClient result = mock(HttpClient.class); HttpResponse response = mock(HttpResponse.class); @@ -57,7 +60,9 @@ protected HttpClient stubHttpClient(int statusCode, String content) throws Excep when(result.execute(any(HttpUriRequest.class))).thenReturn(response); when(LoggingUtils.logResponse(any(HttpResponse.class))).thenReturn("response logged"); - when(entity.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + Function transformation = c -> new ByteArrayInputStream(c.getBytes(StandardCharsets.UTF_8)); + InputStream[] contentsEncoded = Arrays.stream(additionalReturns).map(transformation).toArray(InputStream[]::new); + when(entity.getContent()).thenReturn(transformation.apply(content), contentsEncoded); when(sl.getStatusCode()).thenReturn(statusCode); when(sl.getReasonPhrase()).thenReturn("Test reason"); when(response.getStatusLine()).thenReturn(sl); diff --git a/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java b/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java index 2f00bff45..22650c7e9 100644 --- a/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java +++ b/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java @@ -21,6 +21,8 @@ import static org.junit.Assert.*; import org.junit.Test; import java.net.URI; +import java.util.Collections; +import java.util.List; public class VonageApiResponseExceptionTest { @@ -61,13 +63,19 @@ public void testConstructFromValidJson() { " \"type\": \""+type+"\",\n" + " \"title\": \""+title+"\",\n" + " \"detail\": \""+detail+"\",\n" + - " \"instance\": \""+instance+"\"\n" + + " \"instance\": \""+instance+"\",\n" + + " \"errors\": [\"Bad name\", {\"errorKey\": \"errorValue\"}]\n" + "}" ); actual.statusCode = status; assertEquals(expected, actual); assertEquals(expected.hashCode(), actual.hashCode()); assertEquals(expected.toString(), actual.toString()); + List errors = actual.getErrors(); + assertNotNull(errors); + assertEquals(2, errors.size()); + assertEquals("Bad name", errors.get(0)); + assertEquals(Collections.singletonMap("errorKey", "errorValue"), errors.get(1)); } @Test @@ -113,4 +121,27 @@ public void testToJsonIncludesFields() { assertTrue(crx.dummy); assertEquals(json, crx.toJson()); } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() throws Exception { + class SelfReferencing extends VonageApiResponseException { + @JsonProperty("self") final SelfReferencing self = this; + } + SelfReferencing selfReferencing = new SelfReferencing(); + selfReferencing.toJson(); + try { + throw selfReferencing; + } + catch (Exception ex) { + fail(); + } + } + + @Test(expected = VonageUnexpectedException.class) + public void testFromJsonWithBadConstructorDefinition() throws Exception { + class MissingNoArgs extends VonageApiResponseException { + public MissingNoArgs(boolean dummy) {} + } + VonageApiResponseException.fromJson(MissingNoArgs.class, ""); + } } diff --git a/src/test/java/com/vonage/client/VonageClientTest.java b/src/test/java/com/vonage/client/VonageClientTest.java index 6c9d6ca8f..625c64f42 100644 --- a/src/test/java/com/vonage/client/VonageClientTest.java +++ b/src/test/java/com/vonage/client/VonageClientTest.java @@ -333,6 +333,7 @@ public void testSubClientGetters() { assertNotNull(client.getConversionClient()); assertNotNull(client.getVoiceClient()); assertNotNull(client.getInsightClient()); + assertNotNull(client.getMeetingsClient()); assertNotNull(client.getMessagesClient()); assertNotNull(client.getNumbersClient()); assertNotNull(client.getProactiveConnectClient()); diff --git a/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java new file mode 100644 index 000000000..ae6698ce2 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.time.Instant; +import java.util.UUID; + +public class CreateRoomEndpointTest { + private CreateRoomEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new CreateRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUriAllParameters() throws Exception { + MeetingRoom request = MeetingRoom.builder("Srs bzns meeting") + .metadata("code=1123") + .type(RoomType.LONG_TERM) + .isAvailable(false) + .expireAfterUse(true) + .recordingOptions(RecordingOptions.builder().autoRecord(true).recordOnlyOwner(false).build()) + .initialJoinOptions(InitialJoinOptions.builder().microphoneState(MicrophoneState.OFF).build()) + .callbackUrls(CallbackUrls.builder() + .recordingsCallbackUrl("example.com/re") + .roomsCallbackUrl("example.com/ro") + .sessionsCallbackUrl("example.com/se") + .build() + ) + .availableFeatures(AvailableFeatures.builder() + .isChatAvailable(false) + .isLocaleSwitcherAvailable(false) + .isRecordingAvailable(true) + .isWhiteboardAvailable(false) + .build() + ) + .themeId(UUID.fromString("ef2b46f3-8ebb-437e-a671-272e4990fbc8")) + .joinApprovalLevel(JoinApprovalLevel.EXPLICIT_APPROVAL) + .expiresAt(Instant.MAX) + .uiSettings(UISettings.builder().language(RoomLanguage.IT).build()) + .build(); + + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("POST", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/rooms"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + + String expectedRequest = "{\"expires_at\":\"+1000000000-12-31T23:59:59.999Z\"," + + "\"theme_id\":\"ef2b46f3-8ebb-437e-a671-272e4990fbc8\"," + + "\"display_name\":\"Srs bzns meeting\",\"metadata\":\"code=1123\"," + + "\"is_available\":false,\"expire_after_use\":true,\"type\":\"long_term\"," + + "\"join_approval_level\":\"explicit_approval\",\"recording_options\":" + + "{\"auto_record\":true,\"record_only_owner\":false}," + + "\"initial_join_options\":{\"microphone_state\":\"off\"}," + + "\"ui_settings\":{\"language\":\"it\"},\"callback_urls\":" + + "{\"rooms_callback_url\":\"example.com/ro\"," + + "\"sessions_callback_url\":\"example.com/se\"," + + "\"recordings_callback_url\":\"example.com/re\"}," + + "\"available_features\":{" + + "\"is_recording_available\":true,\"is_chat_available\":false," + + "\"is_whiteboard_available\":false,\"is_locale_switcher_available\":false}}"; + + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\"meeting_code\":\"1234567890\",\"nonsense\":true,\"available_features\":{}}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + MeetingRoom parsed = endpoint.parseResponse(mockResponse); + assertEquals(request, parsed); + + assertEquals("1234567890", parsed.getMeetingCode()); + AvailableFeatures availableFeatures = parsed.getAvailableFeatures(); + assertNotNull(availableFeatures); + assertNull(availableFeatures.getIsChatAvailable()); + assertNull(availableFeatures.getIsRecordingAvailable()); + assertNull(availableFeatures.getIsWhiteboardAvailable()); + assertNull(availableFeatures.getIsLocaleSwitcherAvailable()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new CreateRoomEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/rooms"; + String displayName = "My custom room"; + MeetingRoom request = MeetingRoom.builder(displayName).build(); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + String expectedRequest = "{\"display_name\":\""+displayName+"\"}"; + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("POST", builder.getMethod()); + } + + @Test + public void testParseResponseWithoutRequest() throws Exception { + MeetingRoom parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, "{}")); + assertNotNull(parsed); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java new file mode 100644 index 000000000..dff9354be --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +public class CreateThemeEndpointTest { + private CreateThemeEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new CreateThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequestAllParameters() throws Exception { + Theme request = Theme.builder() + .themeName("Test theme").shortCompanyUrl("https://developer.vonage.com") + .mainColor("#AB34C1").brandText("Vonage").build(); + + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("POST", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + + String expectedRequest = "{\"theme_name\":\"Test theme\"," + + "\"main_color\":\"#AB34C1\"," + + "\"short_company_url\":\"https://developer.vonage.com\"," + + "\"brand_text\":\"Vonage\"}"; + + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + + String expectedResponse = "{\"theme_name\":\"fancy\",\"nonsense\":false,\"branded_favicon\":\"AWS-fav\"}"; + Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, expectedResponse)); + assertEquals(request, parsed); + assertEquals("fancy", parsed.getThemeName()); + assertEquals("AWS-fav", parsed.getBrandedFavicon()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new CreateThemeEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/themes"; + Theme request = Theme.builder().build(); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + String expectedRequest = "{}"; + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("POST", builder.getMethod()); + } + + @Test + public void testParseResponseWithoutRequest() throws Exception { + Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, "{}")); + assertNotNull(parsed); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java b/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java new file mode 100644 index 000000000..067b8ac8c --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class DeleteRecordingEndpointTest { + private DeleteRecordingEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new DeleteRecordingEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID recordingId = UUID.randomUUID(); + RequestBuilder builder = endpoint.makeRequest(recordingId); + assertEquals("DELETE", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/recordings/"+recordingId; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(204, ""))); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + UUID recordingId = UUID.randomUUID(); + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new DeleteRecordingEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/recordings/"+recordingId; + RequestBuilder builder = endpoint.makeRequest(recordingId); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("DELETE", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java new file mode 100644 index 000000000..1eaf34c99 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class DeleteThemeEndpointTest { + private DeleteThemeEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new DeleteThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID themeId = UUID.randomUUID(); + RequestBuilder builder = endpoint.makeRequest(new DeleteThemeRequest(themeId, false)); + assertEquals("DELETE", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId+"?force=false"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(204, ""))); + } + + @Test + public void testCustomUri() throws Exception { + UUID themeId = UUID.randomUUID(); + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new DeleteThemeEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/themes/"+themeId+"?force=true"; + RequestBuilder builder = endpoint.makeRequest(new DeleteThemeRequest(themeId, true)); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("DELETE", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java b/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java new file mode 100644 index 000000000..411d1ec97 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageUnexpectedException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; +import java.util.Arrays; +import java.util.UUID; + +public class FinalizeLogosEndpointTest { + private FinalizeLogosEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new FinalizeLogosEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID themeId = UUID.randomUUID(); + FinalizeLogosRequest request = new FinalizeLogosRequest(themeId, Arrays.asList("col", "fff")); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("PUT", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId+"/finalizeLogos"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, ""))); + } + + @Test + public void testCustomUri() throws Exception { + UUID themeId = UUID.randomUUID(); + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new FinalizeLogosEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/themes/"+themeId+"/finalizeLogos"; + FinalizeLogosRequest request = new FinalizeLogosRequest(themeId, Arrays.asList("lk1", "lk2")); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("PUT", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() { + class SelfRefrencing extends FinalizeLogosRequest { + @JsonProperty("self") final SelfRefrencing self = this; + SelfRefrencing() { + super(null, null); + } + } + new SelfRefrencing().toJson(); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java new file mode 100644 index 000000000..a6b85f4c0 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.util.List; + +public class GetLogoUploadUrlsEndpointTest { + private GetLogoUploadUrlsEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new GetLogoUploadUrlsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + RequestBuilder builder = endpoint.makeRequest(null); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/logos-upload-urls"; + assertEquals(expectedUri, builder.build().getURI().toString()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new GetLogoUploadUrlsEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/themes/logos-upload-urls"; + RequestBuilder builder = endpoint.makeRequest(null); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test + public void testEmptyEdgeCases() throws Exception { + String expectedResponse = "[{\"nonsense\":true}]"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + List parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(1, parsed.size()); + assertNotNull(parsed.get(0)); + + expectedResponse = "[]"; + mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(0, parsed.size()); + + expectedResponse = ""; + mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(0, parsed.size()); + } + + @Test(expected = VonageResponseParseException.class) + public void testParseMalformedResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]")); + } + + @Test(expected = VonageResponseParseException.class) + public void testInvalidContentType() throws Exception { + String json = "[{\"fields\":{\"Content-Type\":\"not-a-mime\"}}]"; + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, json)); + } + + @Test + public void testInvalidLogoType() throws Exception { + String json = "[{\"fields\":{\"logoType\":\"smoll\"}}]"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, json); + List parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(1, parsed.size()); + assertNull(parsed.get(0).getFields().getLogoType()); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java new file mode 100644 index 000000000..0fe9ba565 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class GetRecordingEndpointTest { + private GetRecordingEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new GetRecordingEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID recordingId = UUID.randomUUID(); + RequestBuilder builder = endpoint.makeRequest(recordingId); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/recordings/"+recordingId; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\"nonsense\":0,\"_links\":{\"url\":{}}}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + Recording parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNull(parsed.getUrl()); + assertNull(parsed.getStatus()); + assertNull(parsed.getSessionId()); + assertNull(parsed.getStartedAt()); + assertNull(parsed.getEndedAt()); + assertNull(parsed.getId()); + } + + @Test + public void testCustomUri() throws Exception { + UUID recordingId = UUID.randomUUID(); + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new GetRecordingEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/recordings/"+recordingId; + RequestBuilder builder = endpoint.makeRequest(recordingId); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test(expected = VonageResponseParseException.class) + public void testParseMalformedResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java new file mode 100644 index 000000000..8e6dbd24d --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class GetRoomEndpointTest { + private GetRoomEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new GetRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID roomId = UUID.randomUUID(); + RequestBuilder builder = endpoint.makeRequest(roomId); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/rooms/"+roomId; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, MeetingsClientTest.SAMPLE_ROOM_RESPONSE); + MeetingRoom parsedResponse = endpoint.parseResponse(mockResponse); + MeetingsClientTest.assertEqualsSampleRoom(parsedResponse); + } + + @Test + public void testCustomUri() throws Exception { + UUID roomId = UUID.randomUUID(); + String baseUri = "https://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new GetRoomEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/rooms/"+roomId; + RequestBuilder builder = endpoint.makeRequest(roomId); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java new file mode 100644 index 000000000..1cfde6510 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class GetThemeEndpointTest { + private GetThemeEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new GetThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + UUID themeId = UUID.randomUUID(); + RequestBuilder builder = endpoint.makeRequest(themeId); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\"nonsense\":true}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + Theme parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new GetThemeEndpoint(wrapper); + UUID themeId = UUID.randomUUID(); + String expectedUri = baseUri + "/meetings/themes/"+themeId; + RequestBuilder builder = endpoint.makeRequest(themeId); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java new file mode 100644 index 000000000..bc8605c0b --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.util.List; +import java.util.Locale; + +public class ListDialNumbersEndpointTest { + private ListDialNumbersEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new ListDialNumbersEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + RequestBuilder builder = endpoint.makeRequest(null); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/dial-in-numbers"; + assertEquals(expectedUri, builder.build().getURI().toString()); + String expectedPayload = "[\n" + + "{}, {\n" + + " \"number\": \"17323338801\",\n" + + " \"locale\": \"en-US\",\n" + + " \"display_name\": \"United States\"\n" + + " }," + + " {\"locale\": \"de-DE\",\"unknown property\":0}\n" + + "]"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload); + List parsed = endpoint.parseResponse(mockResponse); + assertEquals(3, parsed.size()); + + DialInNumber empty = parsed.get(0); + assertNotNull(empty); + assertNull(empty.getNumber()); + assertNull(empty.getDisplayName()); + assertNull(empty.getLocale()); + + DialInNumber usa = parsed.get(1); + assertEquals("17323338801", usa.getNumber()); + assertEquals("United States", usa.getDisplayName()); + assertEquals(Locale.US, usa.getLocale()); + + DialInNumber germany = parsed.get(2); + assertEquals(Locale.GERMANY, germany.getLocale()); + assertNull(germany.getDisplayName()); + assertNull(germany.getNumber()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new ListDialNumbersEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/dial-in-numbers"; + RequestBuilder builder = endpoint.makeRequest(null); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java new file mode 100644 index 000000000..ebbe0a3c9 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +public class ListRecordingsEndpointTest { + private ListRecordingsEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new ListRecordingsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequest() throws Exception { + String sessionId = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2M..."; + RequestBuilder builder = endpoint.makeRequest(sessionId); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/sessions/"+sessionId+"/recordings"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedPayload = "{\"_embedded\":{\"recordings\":[{},{\"_links\":{},\"status\":\"uploaded\"}]}}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload); + ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse); + assertEquals(2, parsed.getRecordings().size()); + assertNull(parsed.getRecordings().get(1).getUrl()); + assertEquals(RecordingStatus.UPLOADED, parsed.getRecordings().get(1).getStatus()); + assertEquals("uploaded", RecordingStatus.UPLOADED.toString()); + Recording empty = parsed.getRecordings().get(0); + assertNotNull(empty); + assertNull(empty.getId()); + assertNull(empty.getUrl()); + assertNull(empty.getEndedAt()); + assertNull(empty.getStartedAt()); + assertNull(empty.getStatus()); + assertNull(empty.getSessionId()); + } + + @Test + public void testCustomUri() throws Exception { + String sessionId = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2Jk.." , baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new ListRecordingsEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/sessions/"+sessionId+"/recordings"; + RequestBuilder builder = endpoint.makeRequest(sessionId); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test + public void testEmptyEdgeCases() throws Exception { + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, ""); + ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNull(parsed.getRecordings()); + + mockResponse = TestUtils.makeJsonHttpResponse(200, "{}"); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNull(parsed.getRecordings()); + + mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{}}"); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNull(parsed.getRecordings()); + + mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{\"recordings\":[]}}"); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNotNull(parsed.getRecordings()); + assertEquals(0, parsed.getRecordings().size()); + + mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{\"recordings\":[{}]}}"); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertNotNull(parsed.getRecordings()); + assertEquals(1, parsed.getRecordings().size()); + assertNotNull(parsed.getRecordings().get(0)); + } + + @Test + public void testInvalidRecordingStatus() throws Exception { + String expectedPayload = "{\"_embedded\":{\"recordings\":[{\"status\":\"You're on camera!\"}]}}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload); + ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(1, parsed.getRecordings().size()); + assertNull(parsed.getRecordings().get(0).getStatus()); + } + + @Test(expected = VonageResponseParseException.class) + public void testParseMalformedResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java new file mode 100644 index 000000000..63c58cd49 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.util.Map; + +public class ListRoomsEndpointTest { + private ListRoomsEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new ListRoomsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + ListRoomsRequest request = new ListRoomsRequest(null, null, null, null); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/rooms"; + assertEquals(expectedUri, builder.build().getURI().toString()); + Map params = TestUtils.makeParameterMap(builder.getParameters()); + assertEquals(0, params.size()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\n" + + "\"page_size\":"+request.pageSize+",\"_embedded\":[{}],\"total_items\":3}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + ListRoomsResponse parsed = endpoint.parseResponse(mockResponse); + assertNull(parsed.getLinks()); + assertEquals(1, parsed.getMeetingRooms().size()); + assertNotNull(parsed.getMeetingRooms().get(0)); + assertEquals(3, parsed.getTotalItems().intValue()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new ListRoomsEndpoint(wrapper); + ListRoomsRequest request = new ListRoomsRequest(51, 150, 25, null); + String expectedUri = baseUri + "/meetings/rooms?" + + "start_id="+request.startId+"&end_id="+request.endId+"&page_size="+request.pageSize; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + Map params = TestUtils.makeParameterMap(builder.getParameters()); + assertEquals(3, params.size()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test(expected = VonageResponseParseException.class) + public void testParseMalformedResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java new file mode 100644 index 000000000..85841f7a8 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; +import java.util.List; + +public class ListThemesEndpointTest { + private ListThemesEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new ListThemesEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testDefaultUri() throws Exception { + Void request = null; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes"; + assertEquals(expectedUri, builder.build().getURI().toString()); + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, "[{}]"); + List parsed = endpoint.parseResponse(mockResponse); + assertEquals(1, parsed.size()); + assertNotNull(parsed.get(0)); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new ListThemesEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/themes"; + RequestBuilder builder = endpoint.makeRequest(null); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test + public void testEmptyStringReturnsEmptyList() throws Exception { + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, ""); + List parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(0, parsed.size()); + mockResponse = TestUtils.makeJsonHttpResponse(200, "[]"); + parsed = endpoint.parseResponse(mockResponse); + assertNotNull(parsed); + assertEquals(0, parsed.size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java b/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java new file mode 100644 index 000000000..90ff9bd05 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java @@ -0,0 +1,343 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.VonageUnexpectedException; +import static org.junit.Assert.*; +import org.junit.Test; +import java.net.URI; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +public class MeetingRoomTest { + + @Test + public void testSerializeAndParseAllParameters() { + String + displayName = "Sample room", + metadata = "var=val", + roomsCallbackUrl = "http://example.org/roomsCb", + recordingsCallbackUrl = "http://example.org/recordingsCb", + sessionsCallbackUrl = "http://example.org/sessionsCb"; + RoomType type = RoomType.LONG_TERM; + RoomLanguage language = RoomLanguage.EN; + MicrophoneState microphoneState = MicrophoneState.ON; + JoinApprovalLevel joinApprovalLevel = JoinApprovalLevel.EXPLICIT_APPROVAL; + boolean + isAvailable = false, + expireAfterUse = true, + autoRecord = true, + recordOnlyOwner = false, + isChatAvailable = true, + isLocaleSwitcherAvailable = false, + isRecordingAvailable = true, + isWhiteboardAvailable = false; + RecordingOptions recordingOptions = RecordingOptions.builder() + .autoRecord(autoRecord).recordOnlyOwner(recordOnlyOwner).build(); + InitialJoinOptions initialJoinOptions = InitialJoinOptions.builder() + .microphoneState(microphoneState).build(); + CallbackUrls callbackUrls = CallbackUrls.builder() + .roomsCallbackUrl(roomsCallbackUrl) + .recordingsCallbackUrl(recordingsCallbackUrl) + .sessionsCallbackUrl(sessionsCallbackUrl).build(); + AvailableFeatures availableFeatures = AvailableFeatures.builder() + .isChatAvailable(isChatAvailable) + .isLocaleSwitcherAvailable(isLocaleSwitcherAvailable) + .isRecordingAvailable(isRecordingAvailable) + .isWhiteboardAvailable(isWhiteboardAvailable).build(); + UUID themeId = UUID.randomUUID(); + Instant expiresAt = Instant.now().plusSeconds(3600); + UISettings uiSettings = UISettings.builder().language(language).build(); + + MeetingRoom request = MeetingRoom.builder(displayName) + .metadata(metadata) + .type(type) + .isAvailable(isAvailable) + .expireAfterUse(expireAfterUse) + .recordingOptions(recordingOptions) + .initialJoinOptions(initialJoinOptions) + .callbackUrls(callbackUrls) + .availableFeatures(availableFeatures) + .themeId(themeId) + .joinApprovalLevel(joinApprovalLevel) + .expiresAt(expiresAt) + .uiSettings(uiSettings) + .build(); + + String json = request.toJson(); + assertTrue(json.contains("\"display_name\":\""+displayName+"\"")); + assertTrue(json.contains("\"metadata\":\""+metadata+"\"")); + assertTrue(json.contains("\"type\":\""+type+"\"")); + assertTrue(json.contains("\"is_available\":"+isAvailable)); + assertTrue(json.contains("\"expire_after_use\":"+expireAfterUse)); + assertTrue(json.contains("\"recording_options\":{" + + "\"auto_record\":"+autoRecord + + ",\"record_only_owner\":"+recordOnlyOwner+"}" + )); + assertTrue(json.contains("\"callback_urls\":{" + + "\"rooms_callback_url\":\""+roomsCallbackUrl + + "\",\"sessions_callback_url\":\""+sessionsCallbackUrl + + "\",\"recordings_callback_url\":\""+recordingsCallbackUrl+"\"}" + )); + assertTrue(json.contains("\"available_features\":{" + + "\"is_recording_available\":"+isRecordingAvailable + + ",\"is_chat_available\":"+isChatAvailable + + ",\"is_whiteboard_available\":"+isWhiteboardAvailable + + ",\"is_locale_switcher_available\":"+isLocaleSwitcherAvailable+"}" + )); + assertTrue(json.contains("\"initial_join_options\":{\"microphone_state\":\""+microphoneState+"\"}")); + assertTrue(json.contains("\"ui_settings\":{\"language\":\""+language+"\"}")); + assertTrue(json.contains("\"theme_id\":\""+themeId+"\"")); + assertTrue(json.contains("\"join_approval_level\":\""+joinApprovalLevel+"\"")); + assertTrue(json.contains("\"expires_at\":\""+request.getExpiresAt()+"\"")); + + MeetingRoom response = MeetingRoom.fromJson(json); + assertEquals(request.getDisplayName(), response.getDisplayName()); + assertEquals(request.getMetadata(), response.getMetadata()); + assertEquals(request.getType(), response.getType()); + assertEquals(request.getIsAvailable(), response.getIsAvailable()); + assertEquals(request.getExpireAfterUse(), response.getExpireAfterUse()); + assertEquals(request.getThemeId(), response.getThemeId()); + assertEquals(request.getJoinApprovalLevel(), response.getJoinApprovalLevel()); + assertEquals(request.getExpiresAt(), response.getExpiresAt()); + + RecordingOptions responseRecOpts = response.getRecordingOptions(); + assertEquals(autoRecord, responseRecOpts.getAutoRecord()); + assertEquals(recordOnlyOwner, responseRecOpts.getRecordOnlyOwner()); + + InitialJoinOptions responseJoinOpts = response.getInitialJoinOptions(); + assertEquals(microphoneState, responseJoinOpts.getMicrophoneState()); + + UISettings responseUi = response.getUiSettings(); + assertEquals(language, responseUi.getLanguage()); + + AvailableFeatures responseFeatures = response.getAvailableFeatures(); + assertEquals(isChatAvailable, responseFeatures.getIsChatAvailable()); + assertEquals(isLocaleSwitcherAvailable, responseFeatures.getIsLocaleSwitcherAvailable()); + assertEquals(isRecordingAvailable, responseFeatures.getIsRecordingAvailable()); + assertEquals(isWhiteboardAvailable, responseFeatures.getIsWhiteboardAvailable()); + + CallbackUrls responseCallbacks = response.getCallbackUrls(); + assertEquals(roomsCallbackUrl, responseCallbacks.getRoomsCallbackUrl().toString()); + assertEquals(recordingsCallbackUrl, responseCallbacks.getRecordingsCallbackUrl().toString()); + assertEquals(sessionsCallbackUrl, responseCallbacks.getSessionsCallbackUrl().toString()); + } + + @Test + public void testUpdateFromJson() { + MeetingRoom room = MeetingRoom.builder("Room name 0") + .expiresAt(Instant.now().plusSeconds(7200)) + .type(RoomType.LONG_TERM).expireAfterUse(true) + .availableFeatures(AvailableFeatures.builder() + .isChatAvailable(false).isRecordingAvailable(true).build() + ) + .joinApprovalLevel(JoinApprovalLevel.NONE).build(); + + assertNull(room.getInitialJoinOptions()); + assertNull(room.getUiSettings()); + assertNull(room.getIsAvailable()); + assertNull(room.getCallbackUrls()); + assertFalse(room.getAvailableFeatures().getIsChatAvailable()); + assertTrue(room.getAvailableFeatures().getIsRecordingAvailable()); + assertNull(room.getAvailableFeatures().getIsLocaleSwitcherAvailable()); + assertNull(room.getAvailableFeatures().getIsWhiteboardAvailable()); + + room.updateFromJson("{\"name\":\"Updated name 1\",\"join_approval_level\":\"after_owner_only\"," + + "\"is_available\":true,\"type\":\"instant\",\"ui_settings\":{\"language\":\"ca\"}," + + "\"expire_after_use\":false,\"initial_join_options\":{},\"callback_urls\":{}," + + "\"available_features\":{\"is_locale_switcher_available\":false,\"is_chat_available\":false}}" + ); + + assertTrue(room.getIsAvailable()); + assertFalse(room.getExpireAfterUse()); + assertNotNull(room.getInitialJoinOptions()); + assertNotNull(room.getCallbackUrls()); + assertEquals(RoomLanguage.CA, room.getUiSettings().getLanguage()); + assertEquals(RoomType.INSTANT, room.getType()); + assertEquals(JoinApprovalLevel.AFTER_OWNER_ONLY, room.getJoinApprovalLevel()); + assertFalse(room.getAvailableFeatures().getIsLocaleSwitcherAvailable()); + assertFalse(room.getAvailableFeatures().getIsChatAvailable()); + assertNull(room.getAvailableFeatures().getIsRecordingAvailable()); + assertNull(room.getAvailableFeatures().getIsWhiteboardAvailable()); + + assertThrows(VonageResponseParseException.class, () -> room.updateFromJson("{malformed]")); + } + + @Test + public void testNullOrEmptyDisplayName() { + assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder(null).build()); + assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder(" ").build()); + } + + @Test + public void testExpireAfterUseForInstantRoomType() { + assertThrows(IllegalStateException.class, () -> + MeetingRoom.builder("A room") + .expireAfterUse(false) + .type(RoomType.INSTANT) + .build() + ); + assertThrows(IllegalStateException.class, () -> + MeetingRoom.builder("A room") + .expireAfterUse(true) + .type(RoomType.INSTANT) + .build() + ); + } + + @Test + public void testExpiresAtAndRoomTypeValidation() { + Instant expire = Instant.now().plusSeconds(10_000); + assertNull(MeetingRoom.builder("My Room").expiresAt(expire).build().getType()); + assertEquals(expire.truncatedTo(ChronoUnit.MILLIS), + MeetingRoom.builder("r").type(RoomType.LONG_TERM).expiresAt(expire).build() + .getExpiresAt() + ); + assertThrows(IllegalStateException.class, () -> + MeetingRoom.builder("My Room").expiresAt(expire).type(RoomType.INSTANT).build() + ); + assertThrows(IllegalStateException.class, () -> + MeetingRoom.builder("My Room").type(RoomType.LONG_TERM).build() + ); + } + + @Test + public void testExpiresAtLeast10MinutesFromNow() { + assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder("My Room") + .expiresAt(Instant.now().plusSeconds(599)).build() + ); + assertNotNull( + MeetingRoom.builder("My Room") + .expiresAt(Instant.now().plusSeconds(601)) + .build() + .getExpiresAt() + ); + } + + @Test + public void testFromJsonResponseOnlyFields() { + UUID id = UUID.randomUUID(); + String meetingCode = "9876543201"; + String guestUrl = "https://meetings.vonage.com/"+meetingCode; + String hostUrl = guestUrl + "?participant_token=xyz"; + Instant createdAt = Instant.now(); + RoomLinks links = new RoomLinks(); + (links.guestUrl = new UrlContainer()).href = URI.create(guestUrl); + (links.hostUrl = new UrlContainer()).href = URI.create(hostUrl); + + MeetingRoom response = MeetingRoom.fromJson("{\n" + + "\"id\":\""+id+"\",\n" + + "\"meeting_code\":\""+meetingCode+"\",\n" + + "\"created_at\":\""+createdAt+"\",\n" + + "\"_links\": {\n" + + " \"guest_url\": {\n" + + " \"href\":\""+guestUrl+"\"\n" + + " },\n" + + " \"host_url\": {\n"+ + " \"href\":\""+hostUrl+"\"\n" + + " }\n" + + "}}" + ); + assertEquals(id, response.getId()); + assertEquals(guestUrl, response.getLinks().getGuestUrl().toString()); + assertEquals(hostUrl, response.getLinks().getHostUrl().toString()); + assertEquals(meetingCode, response.getMeetingCode()); + } + + @Test + public void testParseEmptyContainers() { + MeetingRoom response = MeetingRoom.fromJson("{\n" + + "\"initial_join_options\":{},\n" + + "\"recording_options\":{},\n" + + "\"available_features\":{},\n" + + "\"ui_settings\":{},\n" + + "\"callback_urls\":{},\n" + + "\"_links\":{}\n" + + "}"); + + assertNotNull(response.getInitialJoinOptions()); + assertNull(response.getInitialJoinOptions().getMicrophoneState()); + assertNotNull(response.getRecordingOptions()); + assertNull(response.getRecordingOptions().getAutoRecord()); + assertNull(response.getRecordingOptions().getRecordOnlyOwner()); + assertNotNull(response.getAvailableFeatures()); + assertNull(response.getAvailableFeatures().getIsLocaleSwitcherAvailable()); + assertNull(response.getAvailableFeatures().getIsWhiteboardAvailable()); + assertNull(response.getAvailableFeatures().getIsRecordingAvailable()); + assertNull(response.getAvailableFeatures().getIsChatAvailable()); + assertNotNull(response.getUiSettings()); + assertNull(response.getUiSettings().getLanguage()); + assertNotNull(response.getCallbackUrls()); + assertNull(response.getCallbackUrls().getRecordingsCallbackUrl()); + assertNull(response.getCallbackUrls().getRoomsCallbackUrl()); + assertNull(response.getCallbackUrls().getSessionsCallbackUrl()); + assertNotNull(response.getLinks()); + assertNull(response.getLinks().getGuestUrl()); + assertNull(response.getLinks().getHostUrl()); + } + + @Test(expected = VonageUnexpectedException.class) + public void testFromJsonInvalid() { + MeetingRoom.fromJson("{malformed]"); + } + + @Test + public void testFromJsonEmpty() { + MeetingRoom response = MeetingRoom.fromJson("{}"); + assertNull(response.getDisplayName()); + assertNull(response.getMetadata()); + assertNull(response.getType()); + assertNull(response.getIsAvailable()); + assertNull(response.getExpireAfterUse()); + assertNull(response.getRecordingOptions()); + assertNull(response.getInitialJoinOptions()); + assertNull(response.getCallbackUrls()); + assertNull(response.getAvailableFeatures()); + assertNull(response.getThemeId()); + assertNull(response.getJoinApprovalLevel()); + assertNull(response.getCreatedAt()); + assertNull(response.getExpiresAt()); + assertNull(response.getId()); + assertNull(response.getLinks()); + assertNull(response.getMeetingCode()); + assertNull(response.getUiSettings()); + } + + @Test + public void testInvalidEnums() { + MeetingRoom room = MeetingRoom.fromJson( + "{\"join_approval_level\":\"bar\"," + + "\"initial_join_options\":{\"microphone_state\":\"bombastic\"}," + + "\"type\":\"casual\"," + + "\"ui_settings\":{\"language\":\"yoda\"}}" + ); + assertNull(room.getJoinApprovalLevel()); + assertNull(room.getInitialJoinOptions().getMicrophoneState()); + assertNull(room.getType()); + assertNull(room.getUiSettings().getLanguage()); + } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() { + class SelfRefrencing extends MeetingRoom { + @JsonProperty("self") final SelfRefrencing self = this; + } + new SelfRefrencing().toJson(); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java b/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java new file mode 100644 index 000000000..3d4f74018 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java @@ -0,0 +1,786 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vonage.client.ClientTest; +import com.vonage.client.VonageUnexpectedException; +import com.vonage.client.common.HalLinks; +import org.apache.http.client.HttpClient; +import static org.junit.Assert.*; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.function.Supplier; + +public class MeetingsClientTest extends ClientTest { + + static final UUID + RANDOM_ID = UUID.randomUUID(), + ROOM_ID = UUID.fromString("b84cc862-0764-4887-9265-37e8a863164d"); + static final String + SAMPLE_ROOM_RESPONSE = " {\n" + + " \"id\": \""+ROOM_ID+"\",\n" + + " \"display_name\": \"Sina's room\",\n" + + " \"metadata\": \"foo=bar\",\n" + + " \"type\": \"long_term\",\n" + + " \"expires_at\": \"3000-01-17T15:53:03.377Z\",\n" + + " \"recording_options\": {\n" + + " \"auto_record\": false,\n" + + " \"record_only_owner\": false\n" + + " },\n" + + " \"meeting_code\": \"365658578\",\n" + + " \"_links\": {\n" + + " \"host_url\": {\n" + + " \"href\": \"https://meetings.vonage.com/?room_token=365658578&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiYTc1NmI0ZGYtYjk3YS00N2JhLThiYjEtYjcyNzA5ZjNhOGFkIiwiaWF0IjoxNjc0MDcyNDM4fQ.8HbvDfIdPIsz9PFWZZM8psPNu5nZziuh0yBeHXkJoDI\"\n" + + " },\n" + + " \"guest_url\": {\n" + + " \"href\": \"https://meetings.vonage.com/365658578\"\n" + + " }\n" + + " },\n" + + " \"created_at\": \"2023-01-17T16:19:13.518Z\",\n" + + " \"is_available\": true,\n" + + " \"expire_after_use\": false,\n" + + " \"theme_id\": null,\n" + + " \"initial_join_options\": {\n" + + " \"microphone_state\": \"off\"\n" + + " },\n" + + " \"join_approval_level\": \"none\",\n" + + " \"callback_urls\": {\n" + + " \"rooms_callback_url\": \"https://example.com/rooms\",\n" + + " \"sessions_callback_url\": \"https://example.com/sessions\",\n" + + " \"recordings_callback_url\": \"https://example.com/recordings\"\n" + + " },\n" + + " \"ui_settings\": {\n" + + " \"language\": \"de\"\n" + + " },\n" + + " \"available_features\": {\n" + + " \"is_recording_available\": true,\n" + + " \"is_chat_available\": true,\n" + + " \"is_whiteboard_available\": true,\n" + + " \"is_locale_switcher_available\": true\n" + + " }\n" + + " }", + + LIST_ROOMS_RESPONSE = "{\n" + + " \"page_size\": \"3\",\n" + + " \"_embedded\": [\n" + SAMPLE_ROOM_RESPONSE +",\n" + + " {\n" + + " \"id\": \"2f63e54b-adc1-4dda-a27c-24f04c0f1233\",\n" + + " \"display_name\": \"Manchucks\",\n" + + " \"metadata\": null,\n" + + " \"type\": \"instant\",\n" + + " \"recording_options\": {\n" + + " \"auto_record\": false,\n" + + " \"record_only_owner\": false\n" + + " },\n" + + " \"meeting_code\": \"710363620\",\n" + + " \"_links\": {\n" + + " \"host_url\": {\n" + + " \"href\": \"https://meetings.vonage.com/?room_token=710363620&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiZTUxZDk3MTgtNGRmMS00ZWM3LTliNWUtNzRiYWIxNWVjMmQ0IiwiaWF0IjoxNjc0MDcyNDM4fQ.2HVqP5rfE38mtuwRHgPdEJ22RHxGzcnL6g64gngIqYk\"\n" + + " },\n" + + " \"guest_url\": {\n" + + " \"href\": \"https://meetings.vonage.com/710363620\"\n" + + " }\n" + + " },\n" + + " \"created_at\": \"2023-01-18T16:57:27.828Z\",\n" + + " \"is_available\": true,\n" + + " \"expire_after_use\": true,\n" + + " \"theme_id\": null,\n" + + " \"initial_join_options\": {\n" + + " \"microphone_state\": \"default\"\n" + + " },\n" + + " \"join_approval_level\": \"none\",\n" + + " \"ui_settings\": {\n" + + " \"language\": \"de\"\n" + + " },\n" + + " \"available_features\": {\n" + + " \"is_recording_available\": true,\n" + + " \"is_chat_available\": true,\n" + + " \"is_whiteboard_available\": true,\n" + + " \"is_locale_switcher_available\": false\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"_links\": {\n" + + " \"first\": {\n" + + " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3\"\n" + + " },\n" + + " \"self\": {\n" + + " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&start_id=1991085\"\n" + + " },\n" + + " \"prev\": {\n" + + " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&end_id=1991084\"\n" + + " },\n" + + " \"next\": {\n" + + " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&start_id=1994609\"\n" + + " }\n" + + " },\n" + + " \"total_items\": 5\n" + + "}", + + SAMPLE_THEME_RESPONSE = "{\n" + + " \"theme_id\": \"d03e71e5-9336-49c1-8adc-12fb3fa98110\",\n" + + " \"theme_name\": \"Theme1\",\n" + + " \"domain\": \"VBC\",\n" + + " \"account_id\": \"94a99d02-24b2-445a-9526-2846aa5f846\",\n" + + " \"application_id\": \"862f8c7b-d203-4729-68a3-7eded210c9ca\",\n" + + " \"main_color\": \"#12f64e\",\n" + + " \"short_company_url\": \"https://t.co/acme\",\n" + + " \"brand_text\": \"Looney Tunes Ltd.\",\n" + + " \"brand_image_colored\": \"color-key\",\n" + + " \"brand_image_white\": \"white-key\",\n" + + " \"branded_favicon\": \"favicon-key\",\n" + + " \"brand_image_colored_url\": \"https://example.com/color.png\",\n" + + " \"brand_image_white_url\": \"https://example.com/white.png\",\n" + + " \"branded_favicon_url\": \"https://example.com/favicon.png\"\n" + + " }", + + SAMPLE_RECORDING_RESPONSE = "{\n" + + " \"id\": \"497f6eca-6276-4993-bfeb-53cbbbba6f08\",\n" + + " \"session_id\": \"2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNXgya20yQ1Z-fg\",\n" + + " \"started_at\": \"2019-08-24T14:15:22Z\",\n" + + " \"ended_at\": \"2019-11-30T00:59:06Z\",\n" + + " \"status\": \"started\",\n" + + " \"_links\": {\n" + + " \"url\": {\n" + + " \"href\": \"http://example.com/recording.mp4\"\n" + + " }\n" + + " }\n" + + "}"; + + + public MeetingsClientTest() { + client = new MeetingsClient(wrapper); + } + + static void assertEqualsSampleRoom(MeetingRoom parsed) { + assertNotNull(parsed); + assertEquals("b84cc862-0764-4887-9265-37e8a863164d", parsed.getId().toString()); + assertEquals("Sina's room", parsed.getDisplayName()); + assertEquals("foo=bar", parsed.getMetadata()); + assertEquals(RoomType.LONG_TERM, parsed.getType()); + Instant expiresAt = ZonedDateTime.of( + 3000, 1, 17, + 15, 53, 3, + 377_000_000, ZoneId.of("UTC") + ).truncatedTo(ChronoUnit.MILLIS).toInstant(); + assertEquals(expiresAt, parsed.getExpiresAt()); + assertEquals(Instant.parse("3000-01-17T15:53:03.377Z"), parsed.getExpiresAt()); + RecordingOptions recordingOptions = parsed.getRecordingOptions(); + assertNotNull(recordingOptions); + assertFalse(recordingOptions.getAutoRecord()); + assertFalse(recordingOptions.getRecordOnlyOwner()); + assertEquals("365658578", parsed.getMeetingCode()); + RoomLinks roomLinks = parsed.getLinks(); + assertNotNull(roomLinks); + assertTrue(roomLinks.getHostUrl().toString().startsWith("https://meetings.vonage.com/?room_token=365658578&participant_token=ey")); + assertEquals("https://meetings.vonage.com/365658578", roomLinks.getGuestUrl().toString()); + Instant createdAt = ZonedDateTime.of( + 2023, 1, 17, + 16, 19, 13, + 518_000_000, ZoneId.of("UTC") + ).truncatedTo(ChronoUnit.MILLIS).toInstant(); + assertEquals(createdAt, parsed.getCreatedAt()); + assertEquals(Instant.parse("2023-01-17T16:19:13.518Z"), parsed.getCreatedAt()); + assertTrue(parsed.getIsAvailable()); + assertFalse(parsed.getExpireAfterUse()); + assertNull(parsed.getThemeId()); + InitialJoinOptions initialJoinOptions = parsed.getInitialJoinOptions(); + assertNotNull(initialJoinOptions); + assertEquals(MicrophoneState.OFF, initialJoinOptions.getMicrophoneState()); + assertEquals(JoinApprovalLevel.NONE, parsed.getJoinApprovalLevel()); + UISettings uiSettings = parsed.getUiSettings(); + assertNotNull(uiSettings); + CallbackUrls callbackUrls = parsed.getCallbackUrls(); + assertNotNull(callbackUrls); + assertEquals("https://example.com/rooms", callbackUrls.getRoomsCallbackUrl().toString()); + assertEquals("https://example.com/sessions", callbackUrls.getSessionsCallbackUrl().toString()); + assertEquals("https://example.com/recordings", callbackUrls.getRecordingsCallbackUrl().toString()); + assertEquals(RoomLanguage.DE, uiSettings.getLanguage()); + AvailableFeatures availableFeatures = parsed.getAvailableFeatures(); + assertNotNull(availableFeatures); + assertTrue(availableFeatures.getIsRecordingAvailable()); + assertTrue(availableFeatures.getIsChatAvailable()); + assertTrue(availableFeatures.getIsWhiteboardAvailable()); + assertTrue(availableFeatures.getIsLocaleSwitcherAvailable()); + } + + void stubResponseAndAssertEqualsSampleRoom(Supplier call) throws Exception { + assertEqualsSampleRoom(stubResponseAndGet(200, SAMPLE_ROOM_RESPONSE, call)); + } + + static void assertEqualsAvailableRooms(List rooms) { + assertEquals(2, rooms.size()); + assertEqualsSampleRoom(rooms.get(0)); + MeetingRoom otherRoom = rooms.get(1); + assertEquals("2f63e54b-adc1-4dda-a27c-24f04c0f1233", otherRoom.getId().toString()); + assertNull(otherRoom.getMetadata()); + assertEquals(RoomType.INSTANT, otherRoom.getType()); + assertEquals("710363620", otherRoom.getMeetingCode()); + assertTrue(otherRoom.getIsAvailable()); + assertTrue(otherRoom.getExpireAfterUse()); + assertEquals(MicrophoneState.DEFAULT, otherRoom.getInitialJoinOptions().getMicrophoneState()); + assertTrue(otherRoom.getAvailableFeatures().getIsChatAvailable()); + assertFalse(otherRoom.getAvailableFeatures().getIsLocaleSwitcherAvailable()); + assertEquals(JoinApprovalLevel.NONE, otherRoom.getJoinApprovalLevel()); + assertEquals(RoomLanguage.DE, otherRoom.getUiSettings().getLanguage()); + } + + static void assertEqualsAvailableRooms(ListRoomsResponse parsed) { + assertEqualsAvailableRooms(parsed.getMeetingRooms()); + HalLinks links = parsed.getLinks(); + assertEquals("api-us.vonage.com/meetings/rooms?page_size=3", links.getFirstUrl().toString()); + assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&start_id=1991085", links.getSelfUrl().toString()); + assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&end_id=1991084", links.getPrevUrl().toString()); + assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&start_id=1994609", links.getNextUrl().toString()); + } + + static void assertEqualsSampleTheme(Theme parsed) { + assertNotNull(parsed); + assertEquals("d03e71e5-9336-49c1-8adc-12fb3fa98110", parsed.getThemeId().toString()); + assertEquals("Theme1", parsed.getThemeName()); + assertEquals(ThemeDomain.VBC, parsed.getDomain()); + assertEquals("94a99d02-24b2-445a-9526-2846aa5f846", parsed.getAccountId()); + assertEquals("862f8c7b-d203-4729-68a3-7eded210c9ca", parsed.getApplicationId().toString()); + assertEquals("#12f64e", parsed.getMainColor()); + assertEquals("https://t.co/acme", parsed.getShortCompanyUrl()); + assertEquals("Looney Tunes Ltd.", parsed.getBrandText()); + assertEquals("color-key", parsed.getBrandImageColored()); + assertEquals("white-key", parsed.getBrandImageWhite()); + assertEquals("favicon-key", parsed.getBrandedFavicon()); + assertEquals("https://example.com/color.png", parsed.getBrandImageColoredUrl().toString()); + assertEquals("https://example.com/white.png", parsed.getBrandImageWhiteUrl().toString()); + assertEquals("https://example.com/favicon.png", parsed.getBrandedFaviconUrl().toString()); + } + + void stubResponseAndAssertEqualsSampleTheme(Supplier call) throws Exception { + assertEqualsSampleTheme(stubResponseAndGet(200, SAMPLE_THEME_RESPONSE, call)); + } + + static void assertEqualsSampleRecording(Recording parsed) { + assertNotNull(parsed); + assertEquals(UUID.fromString("497f6eca-6276-4993-bfeb-53cbbbba6f08"), parsed.getId()); + assertEquals("2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNXgya20yQ1Z-fg", parsed.getSessionId()); + assertEquals(RecordingStatus.STARTED, parsed.getStatus()); + assertEquals(URI.create("http://example.com/recording.mp4"), parsed.getUrl()); + + assertEquals(Instant.parse("2019-08-24T14:15:22Z"), parsed.getStartedAt()); + Instant startedAt = ZonedDateTime.of( + 2019, 8, 24, + 14, 15, 22, + 0, ZoneId.of("UTC") + ).truncatedTo(ChronoUnit.MILLIS).toInstant(); + assertEquals(startedAt, parsed.getStartedAt()); + + assertEquals(Instant.parse("2019-11-30T00:59:06Z"), parsed.getEndedAt()); + Instant endedAt = ZonedDateTime.of( + 2019, 11, 30, + 0, 59, 6, + 0, ZoneId.of("UTC") + ).truncatedTo(ChronoUnit.MILLIS).toInstant(); + assertEquals(endedAt, parsed.getEndedAt()); + } + + void stubResponseAndAssertEqualsSampleRecording(Supplier call) throws Exception { + assertEqualsSampleRecording(stubResponseAndGet(200, SAMPLE_RECORDING_RESPONSE, call)); + } + + void testPaginatedMeetingRoomsResponse(Supplier> call) throws Exception { + String firstJson = "{\n" + + " \"page_size\": 1000,\n" + + " \"total_items\": 1080,\n" + + " \"_embedded\": [{\"theme_id\":\""+RANDOM_ID+"\"},{}],\n" + + " \"_links\": {\n" + + " \"first\": {\n" + + " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50\"\n" + + " },\n" + + " \"self\": {\n" + + " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293905\"\n" + + " },\n" + + " \"next\": {\n" + + " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293906\"\n" + + " },\n" + + " \"prev\": {\n" + + " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293904\"\n" + + " }\n" + + " }\n" + + "}", + secondJson = "{\n" + + " \"page_size\": 80,\n" + + " \"total_items\": 1080,\n" + + " \"_embedded\": [{},{},{},{},{},{},{\"id\":\""+ROOM_ID+"\"},{}],\n" + + " \"_links\": {\n" + + " \"next\": {\n" + + " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2235997\"\n" + + " }\n" + + " }\n" + + "}"; + + HttpClient httpClient = stubHttpClient(200, firstJson, secondJson); + wrapper.setHttpClient(httpClient); + List rooms = call.get(); + assertEquals(10, rooms.size()); + assertEquals(RANDOM_ID, rooms.get(0).getThemeId()); + assertEquals(ROOM_ID, rooms.get(8).getId()); + } + + void assert401ResponseException(ThrowingRunnable invocation) throws Exception { + int statusCode = 401; + MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson( + "{\n" + + " \"title\": \"Missing Auth\",\n" + + " \"detail\": \"Auth header is required\"\n" + + "}" + ); + + expectedResponse.setStatusCode(statusCode); + String expectedJson = expectedResponse.toJson(); + wrapper.setHttpClient(stubHttpClient(statusCode, expectedJson)); + String failPrefix = "Expected "+expectedResponse.getClass().getSimpleName()+", but got "; + + try { + invocation.run(); + fail(failPrefix + "nothing."); + } + catch (MeetingsResponseException ex) { + assertEquals(expectedResponse, ex); + assertEquals(expectedJson, ex.toJson()); + assertEquals("Missing Auth", ex.getTitle()); + assertEquals("Auth header is required", ex.getDetail()); + assertNull(ex.getType()); + assertNull(ex.getInstance()); + assertNull(ex.getName()); + assertNotNull(ex.getMessage()); + } + catch (Throwable ex) { + fail(failPrefix + ex); + } + } + + void assert400ResponseException(ThrowingRunnable invocation) throws Exception { + int statusCode = 400; + MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson( + "{\n" + + " \"message\": \"Explanation about why validation failed.\",\n" + + " \"name\": \"InputValidationError\",\n" + + " \"status\": "+statusCode+"\n" + + "}" + ); + + String expectedJson = expectedResponse.toJson(); + wrapper.setHttpClient(stubHttpClient(statusCode, expectedJson)); + String failPrefix = "Expected "+expectedResponse.getClass().getSimpleName()+", but got "; + + try { + invocation.run(); + fail(failPrefix + "nothing."); + } + catch (MeetingsResponseException ex) { + assertEquals(statusCode, ex.getStatusCode()); + assertEquals(statusCode, expectedResponse.getStatusCode()); + assertEquals("InputValidationError", ex.getName()); + assertEquals("Explanation about why validation failed.", ex.getMessage()); + assertEquals(expectedResponse.getName(), ex.getName()); + assertEquals(expectedResponse.getMessage(), ex.getMessage()); + assertNull(ex.getType()); + assertNull(ex.getInstance()); + assertNull(expectedResponse.getTitle()); + assertEquals("Test reason", ex.getTitle()); + assertNull(ex.getDetail()); + } + catch (Throwable ex) { + fail(failPrefix + ex); + } + } + + @Test + public void testListRooms() throws Exception { + assertEqualsAvailableRooms(stubResponseAndGet(200, LIST_ROOMS_RESPONSE, client::listRooms)); + assert401ResponseException(client::listRooms); + testPaginatedMeetingRoomsResponse(client::listRooms); + } + + @Test + public void testGetRoom() throws Exception { + stubResponseAndAssertEqualsSampleRoom(() -> client.getRoom(ROOM_ID)); + + stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE, + () -> client.getRoom(null), NullPointerException.class + ); + assert401ResponseException(() -> client.getRoom(ROOM_ID)); + } + + @Test + public void testUpdateRoom() throws Exception { + UpdateRoomRequest request = UpdateRoomRequest.builder().build(); + stubResponseAndAssertEqualsSampleRoom(() -> client.updateRoom(ROOM_ID, request)); + + stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE, + () -> client.updateRoom(ROOM_ID, null), NullPointerException.class + ); + stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE, + () -> client.updateRoom(null, request), NullPointerException.class + ); + assert401ResponseException(() -> client.updateRoom(ROOM_ID, request)); + } + + @Test + public void testCreateRoom() throws Exception { + MeetingRoom request = MeetingRoom.builder("Sample mr").build(); + stubResponseAndAssertEqualsSampleRoom(() -> client.createRoom(request)); + + stubResponseAndAssertThrows(201, SAMPLE_ROOM_RESPONSE, + () -> client.createRoom(null), NullPointerException.class + ); + assert400ResponseException(() -> client.createRoom(request)); + } + + @Test + public void testSearchRoomsByTheme() throws Exception { + assertEqualsAvailableRooms(stubResponseAndGet(200, LIST_ROOMS_RESPONSE, + () -> client.searchRoomsByTheme(RANDOM_ID)) + ); + stubResponseAndAssertThrows(200, LIST_ROOMS_RESPONSE, + () -> client.searchRoomsByTheme(null), NullPointerException.class + ); + assert401ResponseException(() -> client.searchRoomsByTheme(RANDOM_ID)); + testPaginatedMeetingRoomsResponse(() -> client.searchRoomsByTheme(RANDOM_ID)); + } + + @Test + public void testListThemes() throws Exception { + String responseJson = "["+SAMPLE_THEME_RESPONSE+",{\"theme_id\":\""+RANDOM_ID+"\"}]"; + List parsed = stubResponseAndGet(200, responseJson, client::listThemes); + assertEquals(2, parsed.size()); + assertEqualsSampleTheme(parsed.get(0)); + assertEquals(RANDOM_ID, parsed.get(1).getThemeId()); + assert401ResponseException(client::listThemes); + } + + @Test + public void testGetTheme() throws Exception { + stubResponseAndAssertEqualsSampleTheme(() -> client.getTheme(RANDOM_ID)); + + stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE, + () -> client.getTheme(null), NullPointerException.class + ); + assert401ResponseException(() -> client.getTheme(RANDOM_ID)); + } + + @Test + public void testCreateTheme() throws Exception { + Theme request = Theme.builder().brandText("My Company").mainColor("#fff000").build(); + stubResponseAndAssertEqualsSampleTheme(() -> client.createTheme(request)); + + stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE, + () -> client.createTheme(null), NullPointerException.class + ); + assert400ResponseException(() -> client.createTheme(request)); + } + + @Test + public void testUpdateTheme() throws Exception { + Theme request = Theme.builder().build(); + stubResponseAndAssertEqualsSampleTheme(() -> client.updateTheme(RANDOM_ID, request)); + + stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE, + () -> client.updateTheme(RANDOM_ID, null), NullPointerException.class + ); + stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE, + () -> client.updateTheme(null, request), NullPointerException.class + ); + assert400ResponseException(() -> client.updateTheme(RANDOM_ID, request)); + } + + @Test + public void testDeleteTheme() throws Exception { + stubResponseAndRun(204, () -> client.deleteTheme(RANDOM_ID, false)); + + stubResponseAndAssertThrows(204, + () -> client.deleteTheme(null, true), NullPointerException.class + ); + assert401ResponseException(() -> client.deleteTheme(RANDOM_ID, true)); + } + + @Test + public void testListRecordings() throws Exception { + String sessionId = "session_id123"; + String responseJson = "{\"_embedded\":{\"recordings\":[" + SAMPLE_RECORDING_RESPONSE + ",{}]}}"; + stubResponse(200, responseJson); + List recordings = client.listRecordings(sessionId); + assertEquals(2, recordings.size()); + assertEqualsSampleRecording(recordings.get(0)); + assertNotNull(recordings.get(1)); + + responseJson = "{\"_embedded\":{}}"; + stubResponse(200, responseJson); + recordings = client.listRecordings(sessionId); + assertNotNull(recordings); + assertEquals(0, recordings.size()); + + responseJson = "{\"_embedded\":{\"recordings\":[]}}"; + stubResponse(200, responseJson); + recordings = client.listRecordings(sessionId); + assertNotNull(recordings); + assertEquals(0, recordings.size()); + + stubResponseAndAssertThrows(200, + () -> client.listRecordings(null), IllegalArgumentException.class + ); + assert401ResponseException(() -> client.listRecordings(sessionId)); + } + + @Test + public void testGetRecording() throws Exception { + stubResponseAndAssertEqualsSampleRecording(() -> client.getRecording(RANDOM_ID)); + + stubResponseAndAssertThrows(200, + () -> client.getRecording(null), NullPointerException.class + ); + assert401ResponseException(() -> client.getRecording(RANDOM_ID)); + } + + @Test + public void testDeleteRecording() throws Exception { + stubResponseAndRun(204, () -> client.deleteRecording(RANDOM_ID)); + + stubResponseAndAssertThrows(204, + () -> client.deleteRecording(null), NullPointerException.class + ); + assert401ResponseException(() -> client.deleteRecording(RANDOM_ID)); + } + + @Test + public void testListDialNumbers() throws Exception { + String responseJson = "[\n" + + "{\"number\":\"17329672755\",\"display_name\":\"United States\",\"locale\":\"en_US\"},\n" + + "{\"number\":\"48123964788\",\"display_name\":\"Poland\",\"locale\":\"pl_PL\"},\n" + + "{\"number\":\"827047844377\",\"display_name\":\"South Korea\",\"locale\":\"ko_KR\"}\n" + + "]"; + List parsed = stubResponseAndGet(200, responseJson, client::listDialNumbers); + assertEquals(3, parsed.size()); + assertEquals("17329672755", parsed.get(0).getNumber()); + assertEquals("United States", parsed.get(0).getDisplayName()); + assertEquals(Locale.US, parsed.get(0).getLocale()); + assertEquals("48123964788", parsed.get(1).getNumber()); + assertEquals("Poland", parsed.get(1).getDisplayName()); + assertEquals(Locale.forLanguageTag("pl-PL"), parsed.get(1).getLocale()); + assertEquals("827047844377", parsed.get(2).getNumber()); + assertEquals("South Korea", parsed.get(2).getDisplayName()); + assertEquals(Locale.KOREA, parsed.get(2).getLocale()); + assert401ResponseException(client::listDialNumbers); + } + + @Test + public void testUpdateApplication() throws Exception { + UpdateApplicationRequest request = UpdateApplicationRequest.builder().defaultThemeId(RANDOM_ID).build(); + String accId = "account-id_123"; + UUID appId = UUID.randomUUID(); + String responseJson = "{\n" + + " \"application_id\": \""+appId+"\",\n" + + " \"account_id\": \""+accId+"\",\n" + + " \"default_theme_id\": \""+RANDOM_ID+"\"\n" + + "}"; + Application parsed = stubResponseAndGet(200, responseJson, () -> client.updateApplication(request)); + assertEquals(appId, parsed.getApplicationId()); + assertEquals(accId, parsed.getAccountId()); + assertEquals(RANDOM_ID, parsed.getDefaultThemeId()); + + stubResponseAndAssertThrows(200, responseJson, + () -> client.updateApplication(null), NullPointerException.class + ); + assert400ResponseException(() -> client.updateApplication(request)); + } + + @Test + public void testFinalizeLogos() throws Exception { + final String logoKey = "greyscale-logo-key0"; + stubResponseAndRun(200,() -> client.finalizeLogos(RANDOM_ID, Arrays.asList("key1", "l-k-2", "k3"))); + stubResponseAndRun(200, () -> client.finalizeLogos(RANDOM_ID, Collections.singletonList("a"))); + + stubResponseAndAssertThrows(200, + () -> client.finalizeLogos(null, Arrays.asList(logoKey)), NullPointerException.class + ); + stubResponseAndAssertThrows(200, + () -> client.finalizeLogos(RANDOM_ID, null), IllegalArgumentException.class + ); + stubResponseAndAssertThrows(200, + () -> client.finalizeLogos(RANDOM_ID, Collections.emptyList()), IllegalArgumentException.class + ); + + MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson( + "{\n" + + " \"status\": 400,\n" + + " \"name\": \"BadRequestError\",\n" + + " \"message\": \"could not finalize logos\",\n" + + " \"errors\": [\n" + + " {\n" + + " \"logoKey\": \""+logoKey+"\",\n" + + " \"code\": \"invalid_logo_properties\",\n" + + " \"invalidProperty\": \"exceeds_size\"\n" + + " }\n" + + " ]\n" + + "}" + ); + + wrapper.setHttpClient(stubHttpClient(400, expectedResponse.toJson())); + + try { + client.finalizeLogos(RANDOM_ID, Collections.singletonList(logoKey)); + fail("Expected "+expectedResponse.getClass().getSimpleName()); + } + catch (MeetingsResponseException ex) { + assertEquals(400, ex.getStatusCode()); + assertEquals(ex.getStatusCode(), expectedResponse.getStatusCode()); + assertEquals("BadRequestError", ex.getName()); + assertEquals("could not finalize logos", ex.getMessage()); + assertEquals(expectedResponse.getName(), ex.getName()); + assertEquals(expectedResponse.getMessage(), ex.getMessage()); + assertNull(ex.getType()); + assertNull(ex.getInstance()); + assertNull(expectedResponse.getTitle()); + assertEquals("Test reason", ex.getTitle()); + assertNull(ex.getDetail()); + + List errors = ex.getErrors(); + assertNotNull(errors); + assertEquals(1, errors.size()); + @SuppressWarnings("unchecked") + Map firstError = (Map) errors.get(0); + assertNotNull(firstError); + assertEquals(3, firstError.size()); + assertEquals(firstError.get("logoKey"), logoKey); + assertEquals(firstError.get("code"), "invalid_logo_properties"); + assertEquals(firstError.get("invalidProperty"), "exceeds_size"); + assertEquals(expectedResponse.getErrors(), errors); + } + } + + @Test + public void testListLogoUploadUrls() throws Exception { + String responseJson = "[\n" + + "{}, {\n" + + " \"url\": \"https://storage-url.com\",\n" + + " \"fields\": {\n" + + " \"Content-Type\": \"image/png\",\n" + + " \"key\": \"auto-expiring-temp/logos/white/ca63a155-d\",\n" + + " \"logoType\": \"white\",\n" + + " \"bucket\": \"s3\",\n" + + " \"X-Amz-Algorithm\": \"stringA\",\n" + + " \"X-Amz-Credential\": \"stringC\",\n" + + " \"X-Amz-Date\": \"stringD\",\n" + + " \"X-Amz-Security-Token\": \"stringT\",\n" + + " \"Policy\": \"stringP\",\n" + + " \"X-Amz-Signature\": \"stringS\"\n" + + " }\n" + + " }, {\"url\": \"http://example.com\"}\n" + + "]"; + stubResponse(200, responseJson); + List parsed = client.listLogoUploadUrls(); + assertEquals(3, parsed.size()); + assertNotNull(parsed.get(0)); + assertEquals("http://example.com", parsed.get(2).getUrl().toString()); + LogoUploadsUrlResponse sample = parsed.get(1); + LogoUploadsUrlResponse.Fields fields = sample.getFields(); + assertNotNull(fields); + assertEquals("https://storage-url.com", sample.getUrl().toString()); + assertEquals("image/png", fields.getContentType().toString()); + assertEquals("auto-expiring-temp/logos/white/ca63a155-d", fields.getKey()); + assertEquals(LogoType.WHITE, fields.getLogoType()); + assertEquals("s3", fields.getBucket()); + assertEquals("stringA", fields.getAmzAlgorithm()); + assertEquals("stringC", fields.getAmzCredential()); + assertEquals("stringD", fields.getAmzDate()); + assertEquals("stringT", fields.getAmzSecurityToken()); + assertEquals("stringP", fields.getPolicy()); + assertEquals("stringS", fields.getAmzSignature()); + assert401ResponseException(client::listLogoUploadUrls); + } + + @Test + public void testGetUploadDetailsForLogoType() throws Exception { + String whiteKey = "blanc", colourKey = "colour", faviconKey = "favourite"; + String responseJson = "[\n" + + "{\"fields\":{\"key\": \""+whiteKey+"\",\"logoType\": \"white\"}},\n" + + "{\"fields\":{\"key\": \""+colourKey+"\",\"logoType\": \"colored\"}},\n" + + "{\"fields\":{\"key\": \""+faviconKey+"\",\"logoType\": \"favicon\"}}]"; + + stubResponse(200, responseJson); + LogoUploadsUrlResponse colour = client.getUploadDetailsForLogoType(LogoType.COLORED); + assertEquals(LogoType.COLORED, colour.getFields().getLogoType()); + assertEquals(colourKey, colour.getFields().getKey()); + + stubResponse(200, responseJson); + LogoUploadsUrlResponse favicon = client.getUploadDetailsForLogoType(LogoType.FAVICON); + assertEquals(LogoType.FAVICON, favicon.getFields().getLogoType()); + assertEquals(faviconKey, favicon.getFields().getKey()); + + stubResponse(200, responseJson); + LogoUploadsUrlResponse white = client.getUploadDetailsForLogoType(LogoType.WHITE); + assertEquals(LogoType.WHITE, white.getFields().getLogoType()); + assertEquals(whiteKey, white.getFields().getKey()); + } + + @Test + public void testUploadLogo() throws Exception { + LogoUploadsUrlResponse details = new ObjectMapper().readerFor(LogoUploadsUrlResponse.class).readValue(( + "{\"url\":\"https://s3.amazonaws.com/roomservice-whitelabel-logos-prod\",\"fields\":{" + + "\"Content-Type\":\"image/png\",\"key\":\"auto-expiring-temp/logos/colored/REDACTED\"," + + "\"logoType\":\"colored\",\"bucket\":\"roomservice-whitelabel-logos-prod\"," + + "\"X-Amz-Algorithm\":\"AWS4-HMAC-SHA256\",\"X-Amz-Credential\":" + + "\"REDACTED/20230130/us-east-1/s3/aws4_request\",\"X-Amz-Date\":\"20230130T113037Z\"," + + "\"X-Amz-Security-Token\":\"REDACTED\",\"Policy\":\"REDACTED\",\"X-Amz-Signature\":\"REDACTED\"}}" + )); + Path image = Paths.get("/path/to/logo.png"); + + client.httpClient = stubHttpClient(204); + client.uploadLogo(image, details); + + client.httpClient = stubHttpClient(400); + assertThrows(MeetingsResponseException.class, () -> client.uploadLogo(image, details)); + + client.httpClient = stubHttpClient(200); + when(client.httpClient.execute(any())).thenThrow(IOException.class); + assertThrows(VonageUnexpectedException.class, () -> client.uploadLogo(image, details)); + } + + @Test + public void testUpdateThemeLogo() throws Exception { + String responseJson = "[{\"url\":\"" + + "https://s3.amazonaws.com/roomservice-whitelabel-logos-prod\",\"fields\":{" + + "\"Content-Type\":\"image/png\",\"key\":\"auto-expiring-temp/logos/favicon/REDACTED\"," + + "\"logoType\":\"favicon\",\"bucket\":\"roomservice-whitelabel-logos-prod\"," + + "\"X-Amz-Algorithm\":\"AWS4-HMAC-SHA256\",\"X-Amz-Credential\":" + + "\"REDACTED/20230130/us-east-1/s3/aws4_request\",\"X-Amz-Date\":\"20230130T113037Z\"," + + "\"X-Amz-Security-Token\":\"REDACTED\",\"Policy\":\"REDACTED\",\"X-Amz-Signature\":\"REDACTED\"}}]"; + + Path image = Paths.get("/path/to/logo.png"); + stubResponse(200, responseJson); + client.httpClient = stubHttpClient(204); + client.updateThemeLogo(RANDOM_ID, LogoType.FAVICON, image); + } +} diff --git a/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java b/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java new file mode 100644 index 000000000..240833107 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.VonageResponseParseException; +import static junit.framework.TestCase.*; +import org.junit.Test; +import java.net.URI; +import java.time.Instant; +import java.util.UUID; + +public class MeetingsEventCallbackTest { + static final UUID + ROOM_ID = UUID.randomUUID(), + RECORDING_ID = UUID.randomUUID(), + PARTICIPANT_ID = UUID.randomUUID(); + static final String + PARTICIPANT_NAME = "John Smith", + PARTICIPANT_TYPE = "Guest", + SESSION_ID = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNX20yQ1Z-fg"; + static final Instant + CREATED_AT = Instant.parse("2023-06-06T06:45:07.135Z"), + EXPIRES_AT = Instant.parse("2023-06-06T06:55:07.134Z"), + STARTED_AT = Instant.parse("2023-06-06T07:15:13.974Z"), + ENDED_AT = Instant.now(); + static final URI RECORDING_URL = URI.create("https://prod-meetings-recordings.s3.amazonaws.com/123/UUID/archive.mp4"); + static final Boolean IS_HOST = true; + static final Integer DURATION = 3598; + static final RoomType ROOM_TYPE = RoomType.INSTANT; + + @Test + public void testRoomExpired() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"room:expired\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"room_type\": \""+ROOM_TYPE+"\",\n" + + " \"expires_at\": \""+EXPIRES_AT+"\",\n" + + " \"created_at\": \""+CREATED_AT+"\"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.ROOM_EXPIRED, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertEquals(ROOM_TYPE, event.getRoomType()); + assertEquals(CREATED_AT, event.getCreatedAt()); + assertNull(event.getStartedAt()); + assertEquals(EXPIRES_AT, event.getExpiresAt()); + assertNull(event.getEndedAt()); + assertNull(event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertNull(event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test + public void testSessionStarted() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"session:started\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"started_at\": \""+STARTED_AT+"\"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.SESSION_STARTED, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertEquals(STARTED_AT, event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertNull(event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertNull(event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test + public void testSessionEnded() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"session:ended\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"started_at\": \""+STARTED_AT+"\",\n" + + " \"ended_at\": \""+ENDED_AT+"\"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.SESSION_ENDED, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertEquals(STARTED_AT, event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertEquals(ENDED_AT, event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertNull(event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test + public void testRecordingStarted() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"recording:started\",\n" + + " \"recording_id\": \""+RECORDING_ID+"\",\n" + + " \"session_id\": \""+SESSION_ID+"\"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.RECORDING_STARTED, event.getEvent()); + assertNull(event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertNull(event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertNull(event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertEquals(RECORDING_ID, event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test + public void testRecordingEnded() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"recording:ended\",\n" + + " \"recording_id\": \""+RECORDING_ID+"\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"started_at\": \""+STARTED_AT+"\",\n" + + " \"ended_at\": \""+ENDED_AT+"\",\n" + + " \"duration\": "+DURATION+"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.RECORDING_ENDED, event.getEvent()); + assertNull(event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertEquals(STARTED_AT, event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertEquals(ENDED_AT, event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertEquals(RECORDING_ID, event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertEquals(DURATION, event.getDuration()); + } + + @Test + public void testRecordingUploaded() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"recording:uploaded\",\n" + + " \"recording_id\": \""+RECORDING_ID+"\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"started_at\": \""+STARTED_AT+"\",\n" + + " \"ended_at\": \""+ENDED_AT+"\",\n" + + " \"duration\": \""+DURATION+"\",\n" + + " \"url\": \""+ RECORDING_URL +"\"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.RECORDING_UPLOADED, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertEquals(STARTED_AT, event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertEquals(ENDED_AT, event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertNull(event.getParticipantId()); + assertNull(event.getParticipantName()); + assertNull(event.getParticipantType()); + assertNull(event.getIsHost()); + assertEquals(RECORDING_ID, event.getRecordingId()); + assertEquals(RECORDING_URL, event.getRecordingUrl()); + assertEquals(DURATION, event.getDuration()); + } + + @Test + public void testParticipantJoined() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"session:participant:joined\",\n" + + " \"participant_id\": \""+PARTICIPANT_ID+"\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"name\": \""+PARTICIPANT_NAME+"\",\n" + + " \"type\": \""+PARTICIPANT_TYPE+"\",\n" + + " \"is_host\": "+IS_HOST+"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.SESSION_PARTICIPANT_JOINED, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertNull(event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertNull(event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertEquals(PARTICIPANT_ID, event.getParticipantId()); + assertEquals(PARTICIPANT_NAME, event.getParticipantName()); + assertEquals(PARTICIPANT_TYPE, event.getParticipantType()); + assertEquals(IS_HOST, event.getIsHost()); + assertNull(event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test + public void testParticipantLeft() { + MeetingsEventCallback event = MeetingsEventCallback.fromJson( + "{\n" + + " \"event\": \"session:participant:left\",\n" + + " \"participant_id\": \""+PARTICIPANT_ID+"\",\n" + + " \"session_id\": \""+SESSION_ID+"\",\n" + + " \"room_id\": \""+ROOM_ID+"\",\n" + + " \"name\": \""+PARTICIPANT_NAME+"\",\n" + + " \"type\": \""+PARTICIPANT_TYPE+"\",\n" + + " \"is_host\": "+IS_HOST+"\n" + + "}" + ); + + assertNotNull(event); + assertEquals(EventType.SESSION_PARTICIPANT_LEFT, event.getEvent()); + assertEquals(ROOM_ID, event.getRoomId()); + assertNull(event.getRoomType()); + assertNull(event.getCreatedAt()); + assertNull(event.getStartedAt()); + assertNull(event.getExpiresAt()); + assertNull(event.getEndedAt()); + assertEquals(SESSION_ID, event.getSessionId()); + assertEquals(PARTICIPANT_ID, event.getParticipantId()); + assertEquals(PARTICIPANT_NAME, event.getParticipantName()); + assertEquals(PARTICIPANT_TYPE, event.getParticipantType()); + assertEquals(IS_HOST, event.getIsHost()); + assertNull(event.getRecordingId()); + assertNull(event.getRecordingUrl()); + assertNull(event.getDuration()); + } + + @Test(expected = VonageResponseParseException.class) + public void testFromJsonInvalid() { + MeetingsEventCallback.fromJson("{malformed]"); + } +} diff --git a/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java b/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java new file mode 100644 index 000000000..aa289488f --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpResponseException; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; +import java.util.Map; +import java.util.UUID; + +public class SearchThemeRoomsEndpointTest { + private SearchThemeRoomsEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new SearchThemeRoomsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequestAllParameters() throws Exception { + ListRoomsRequest request = new ListRoomsRequest(21, 33, 50, UUID.randomUUID()); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("GET", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+request.themeId+ + "/rooms?start_id="+request.startId+"&end_id="+request.endId+"&page_size="+request.pageSize; + assertEquals(expectedUri, builder.build().getURI().toString()); + Map params = TestUtils.makeParameterMap(builder.getParameters()); + assertEquals(3, params.size()); + + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\n" + + "\"page_size\":"+request.pageSize+",\"_embedded\":[{},{},{}," + + "{\"type\":\"instant\"},{\"nonsense\":0}],\"_links\":{\n" + + "\"self\":{\"href\":\"https://vonage.com\"},\"prev\":{},\"next\":{\"href\":null,\"no\":1}}}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + ListRoomsResponse parsed = endpoint.parseResponse(mockResponse); + assertEquals(request.pageSize, parsed.getPageSize()); + assertEquals(5, parsed.getMeetingRooms().size()); + assertEquals(RoomType.INSTANT, parsed.getMeetingRooms().get(3).getType()); + assertNull(parsed.getLinks().getNextUrl()); + assertNull(parsed.getLinks().getPrevUrl()); + assertNull(parsed.getLinks().getFirstUrl()); + assertEquals("https://vonage.com", parsed.getLinks().getSelfUrl().toString()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new SearchThemeRoomsEndpoint(wrapper); + ListRoomsRequest request = new ListRoomsRequest(1, null, null, UUID.randomUUID()); + String expectedUri = baseUri + "/meetings/themes/"+request.themeId+"/rooms?start_id="+request.startId; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + Map params = TestUtils.makeParameterMap(builder.getParameters()); + assertEquals(1, params.size()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("GET", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/ThemeTest.java b/src/test/java/com/vonage/client/meetings/ThemeTest.java new file mode 100644 index 000000000..7dc67ee24 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/ThemeTest.java @@ -0,0 +1,211 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.VonageResponseParseException; +import com.vonage.client.VonageUnexpectedException; +import static org.junit.Assert.*; +import org.junit.Test; +import java.net.URI; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class ThemeTest { + + @Test + public void testSerializeAndParseCommonParameters() { + String themeName = "Test theme 1"; + String mainColor = "#1e1e1f"; + String shortCompanyUrl = "t.co/VonageDev"; + String brandText = "Nexmo"; + Theme request = Theme.builder() + .themeName(themeName) + .mainColor(mainColor) + .shortCompanyUrl(shortCompanyUrl) + .brandText(brandText) + .build(); + + String json = request.toJson(); + assertTrue(json.contains("\"theme_name\":\""+themeName+"\"")); + assertTrue(json.contains("\"main_color\":\""+mainColor+"\"")); + assertTrue(json.contains("\"short_company_url\":\""+shortCompanyUrl+"\"")); + assertTrue(json.contains("\"brand_text\":\""+brandText+"\"")); + + Theme response = Theme.fromJson(json); + assertEquals(request.getThemeName(), response.getThemeName()); + assertEquals(request.getMainColor(), response.getMainColor()); + assertEquals(request.getShortCompanyUrl(), response.getShortCompanyUrl()); + assertEquals(request.getBrandText(), response.getBrandText()); + } + + @Test + public void testFromJsonAllFields() { + UUID themeId = UUID.randomUUID(); + String themeName = "Another name"; + ThemeDomain domain = ThemeDomain.VCP; + String accountId = "account identifier"; + UUID applicationId = UUID.randomUUID(); + String mainColor = "#fff000"; + String shortCompanyUrl = "www.example.com"; + String brandText = "Test brand"; + String brandImageColored = "color_key"; + String brandImageWhite = "white_key"; + String brandedFavicon = "favicon_key"; + URI brandImageColoredUrl = URI.create("https://example.com/colour.jpg"); + URI brandImageWhiteUrl = URI.create("https://example.com/white.jpg"); + URI brandedFaviconUrl = URI.create("https://example.com/favicon.jpg"); + + Theme response = Theme.fromJson("{\n" + + "\"theme_id\":\""+themeId+"\",\n" + + "\"theme_name\":\""+themeName+"\",\n" + + "\"domain\":\""+domain+"\",\n" + + "\"account_id\":\""+accountId+"\",\n" + + "\"application_id\":\""+applicationId+"\",\n" + + "\"main_color\":\""+mainColor+"\",\n" + + "\"short_company_url\":\""+shortCompanyUrl+"\",\n" + + "\"brand_text\":\""+brandText+"\",\n" + + "\"brand_image_colored\":\""+brandImageColored+"\",\n" + + "\"brand_image_white\":\""+brandImageWhite+"\",\n" + + "\"branded_favicon\":\""+brandedFavicon+"\",\n" + + "\"brand_image_colored_url\":\""+brandImageColoredUrl+"\",\n" + + "\"brand_image_white_url\":\""+brandImageWhiteUrl+"\",\n" + + "\"branded_favicon_url\":\""+brandedFaviconUrl+"\"\n" + + "}"); + + assertEquals(themeId, response.getThemeId()); + assertEquals(themeName, response.getThemeName()); + assertEquals(domain, response.getDomain()); + assertEquals(accountId, response.getAccountId()); + assertEquals(applicationId, response.getApplicationId()); + assertEquals(mainColor, response.getMainColor()); + assertEquals(shortCompanyUrl, response.getShortCompanyUrl()); + assertEquals(brandText, response.getBrandText()); + assertEquals(brandImageColored, response.getBrandImageColored()); + assertEquals(brandImageWhite, response.getBrandImageWhite()); + assertEquals(brandedFavicon, response.getBrandedFavicon()); + assertEquals(brandImageColoredUrl, response.getBrandImageColoredUrl()); + assertEquals(brandImageWhiteUrl, response.getBrandImageWhiteUrl()); + assertEquals(brandedFaviconUrl, response.getBrandedFaviconUrl()); + } + + @Test + public void testUpdateFromJson() { + Theme theme = Theme.builder().brandText("Initial text").mainColor("#a1b2c3").build(); + assertEquals("Initial text", theme.getBrandText()); + assertNull(theme.getThemeName()); + assertNull(theme.getThemeId()); + + theme.updateFromJson("{\"theme_id\":\""+UUID.randomUUID()+"\"," + + "\"brand_text\":\"Nexmo\",\"theme_name\":\"test Theme\"}" + ); + assertNotNull(theme.getThemeId()); + assertEquals("#a1b2c3", theme.getMainColor()); + assertEquals("Nexmo", theme.getBrandText()); + assertNotNull(theme.getThemeName()); + + assertThrows(VonageResponseParseException.class, () -> theme.updateFromJson("{malformed]")); + } + + @Test(expected = VonageUnexpectedException.class) + public void testFromJsonInvalid() { + Theme.fromJson("{malformed]"); + } + + @Test + public void testFromJsonEmpty() { + Theme response = Theme.fromJson("{}"); + assertNull(response.getThemeId()); + assertNull(response.getThemeName()); + assertNull(response.getDomain()); + assertNull(response.getAccountId()); + assertNull(response.getApplicationId()); + assertNull(response.getMainColor()); + assertNull(response.getShortCompanyUrl()); + assertNull(response.getBrandText()); + assertNull(response.getBrandImageColored()); + assertNull(response.getBrandImageWhite()); + assertNull(response.getBrandedFavicon()); + assertNull(response.getBrandImageColoredUrl()); + assertNull(response.getBrandImageWhiteUrl()); + assertNull(response.getBrandedFaviconUrl()); + } + + @Test + public void testInvalidDomain() { + Theme parsed = Theme.fromJson("{\"domain\":\"Nexmo Business Meetings\"}"); + assertNull(parsed.getDomain()); + } + + @Test + public void testSerializeEmpty() { + String json = Theme.builder().build().toJson(); + assertEquals("{}", json); + } + + @Test + public void testBrandTextValidation() { + String padding = IntStream.range(1, 200) + .mapToObj(i -> Character.toString((char) (((char) i) % 100))) + .collect(Collectors.joining()); + + assertEquals(199, Theme.builder().brandText(padding).build().getBrandText().length()); + assertEquals(200, Theme.builder().brandText("0"+padding).build().getBrandText().length()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().brandText("ab"+padding).build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().brandText(" ").build()); + } + + @Test + public void testThemeNameValidation() { + String padding = IntStream.range(1, 200) + .mapToObj(i -> Character.toString((char) (((char) i) % 126))) + .collect(Collectors.joining()); + + assertEquals(199, Theme.builder().themeName(padding).build().getThemeName().length()); + assertEquals(200, Theme.builder().themeName("0"+padding).build().getThemeName().length()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().themeName("ab"+padding).build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().themeName(" ").build()); + } + + @Test + public void testShortCompanyUrlValidation() { + String padding = IntStream.range(1, 128) + .mapToObj(i -> Character.toString((char) (((char) i) % 126))) + .collect(Collectors.joining()); + + assertEquals(127, Theme.builder().shortCompanyUrl(padding).build().getShortCompanyUrl().length()); + assertEquals(128, Theme.builder().shortCompanyUrl("0"+padding).build().getShortCompanyUrl().length()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().shortCompanyUrl("ab"+padding).build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().shortCompanyUrl(" ").build()); + } + + @Test + public void testMainColorValidation() { + assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor(" ").build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("").build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("#######").build()); + assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("#gggggg").build()); + } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() { + class SelfRefrencing extends Theme { + @JsonProperty("self") final SelfRefrencing self = this; + } + new SelfRefrencing().toJson(); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java new file mode 100644 index 000000000..971c10a6c --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.*; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class UpdateApplicationEndpointTest { + private UpdateApplicationEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new UpdateApplicationEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequest() throws Exception { + UUID themeId = UUID.fromString("e86a7335-35fe-45e1-b961-5777d4748022"); + UpdateApplicationRequest request = UpdateApplicationRequest.builder() + .defaultThemeId(themeId) + .build(); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("PATCH", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/applications"; + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedRequest = "{\"update_details\": {\"default_theme_id\":\""+themeId+"\"}}"; + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + String expectedResponse = "{\n" + + " \"application_id\": \"48ac72d0-a829-4896-a067-dcb1c2b0f30c\",\n" + + " \"account_id\": \"\",\n" + + " \"default_theme_id\": \""+themeId+"\", \"unknown\": 1\n" + + "}"; + HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse); + Application parsed = endpoint.parseResponse(mockResponse); + assertEquals(themeId, parsed.getDefaultThemeId()); + assertEquals("48ac72d0-a829-4896-a067-dcb1c2b0f30c", parsed.getApplicationId().toString()); + assertEquals("", parsed.getAccountId()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new UpdateApplicationEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/applications"; + UpdateApplicationRequest request = UpdateApplicationRequest.builder().defaultThemeId(UUID.randomUUID()).build(); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("PATCH", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() { + class SelfRefrencing extends UpdateApplicationRequest { + @JsonProperty("self") final SelfRefrencing self = this; + SelfRefrencing() { + super(UpdateApplicationRequest.builder().defaultThemeId(UUID.randomUUID())); + } + } + new SelfRefrencing().toJson(); + } + + @Test(expected = VonageResponseParseException.class) + public void testParseMalformedResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java new file mode 100644 index 000000000..6bd9b4dd0 --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.VonageUnexpectedException; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; +import java.time.Instant; +import java.util.UUID; + +public class UpdateRoomEndpointTest { + private UpdateRoomEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new UpdateRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequestAllParameters() throws Exception { + UUID roomId = MeetingsClientTest.ROOM_ID, themeId = UUID.fromString("5af77e5e-410d-489c-a30e-21aaf8482715"); + UpdateRoomRequest request = UpdateRoomRequest.builder() + .expireAfterUse(false) + .initialJoinOptions(InitialJoinOptions.builder().microphoneState(MicrophoneState.ON).build()) + .callbackUrls(CallbackUrls.builder().build()) + .availableFeatures(AvailableFeatures.builder().build()) + .themeId(themeId) + .joinApprovalLevel(JoinApprovalLevel.AFTER_OWNER_ONLY) + .expiresAt(Instant.MAX.minusSeconds(86400)) + .build(); + + request.roomId = roomId; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("PATCH", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/rooms/"+roomId; + assertEquals(expectedUri, builder.build().getURI().toString()); + + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + String expectedRequest = "{\"update_details\":{\"expire_after_use\":false,\"initial_join_options\":{\"microphone_state\":\"on\"}," + + "\"callback_urls\":{},\"available_features\":{},\"join_approval_level\":\"after_owner_only\"," + + "\"theme_id\":\"5af77e5e-410d-489c-a30e-21aaf8482715\",\"expires_at\":\"+1000000000-12-30T23:59:59.999Z\"}}"; + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + MeetingRoom parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{}")); + assertNotNull(parsed); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new UpdateRoomEndpoint(wrapper); + String expectedUri = baseUri + "/meetings/rooms/"+MeetingsClientTest.RANDOM_ID; + UpdateRoomRequest request = UpdateRoomRequest.builder().build(); + request.roomId = MeetingsClientTest.RANDOM_ID; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + assertEquals("{\"update_details\":{}}", EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("PATCH", builder.getMethod()); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } + + @Test(expected = VonageUnexpectedException.class) + public void triggerJsonProcessingException() { + class SelfRefrencing extends UpdateRoomRequest { + @JsonProperty("self") final SelfRefrencing self = this; + SelfRefrencing() { + super(UpdateRoomRequest.builder()); + } + } + new SelfRefrencing().toJson(); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java new file mode 100644 index 000000000..330c8dcfd --- /dev/null +++ b/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2023 Vonage + * + * 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 com.vonage.client.meetings; + +import com.vonage.client.HttpConfig; +import com.vonage.client.HttpWrapper; +import com.vonage.client.TestUtils; +import com.vonage.client.auth.JWTAuthMethod; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import java.util.UUID; + +public class UpdateThemeEndpointTest { + private UpdateThemeEndpoint endpoint; + + @Before + public void setUp() { + endpoint = new UpdateThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0]))); + } + + @Test + public void testMakeRequestAllParameters() throws Exception { + String themeId = "2f310f5a-995c-4f7e-9072-6aafbf6a4205"; + Theme request = Theme.builder() + .shortCompanyUrl("developer.vonage.com").brandText("Vonage (purple)") + .themeName("Vonage theme").mainColor("#8a1278").build(); + + request.themeId = UUID.fromString(themeId); + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals("PATCH", builder.getMethod()); + String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId; + assertEquals(expectedUri, builder.build().getURI().toString()); + + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + String expectedRequest = "{\"update_details\":{\"theme_name\":\"Vonage theme\",\"main_color\":" + + "\"#8a1278\",\"short_company_url\":\"developer.vonage.com\",\"brand_text\":\"Vonage (purple)\"}}"; + assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + String expectedResponse = "{\"nonsense\":false,\"brand_image_colored_url\":\"ftp://example.com/logo.png\"}"; + Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, expectedResponse)); + assertNotNull(parsed); + assertEquals(request, parsed); + assertEquals("ftp://example.com/logo.png", parsed.getBrandImageColoredUrl().toString()); + } + + @Test + public void testCustomUri() throws Exception { + String baseUri = "http://example.com"; + HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build()); + endpoint = new UpdateThemeEndpoint(wrapper); + Theme request = Theme.builder().build(); + request.themeId = UUID.randomUUID(); + String expectedUri = baseUri + "/meetings/themes/"+request.themeId; + RequestBuilder builder = endpoint.makeRequest(request); + assertEquals(expectedUri, builder.build().getURI().toString()); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue()); + assertEquals("{\"update_details\":{}}", EntityUtils.toString(builder.getEntity())); + assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue()); + assertEquals("PATCH", builder.getMethod()); + } + + @Test + public void testParseResponseWithoutRequest() throws Exception { + Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{}")); + assertNotNull(parsed); + } + + @Test(expected = MeetingsResponseException.class) + public void testUnsuccessfulResponse() throws Exception { + endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}")); + } +} \ No newline at end of file diff --git a/src/test/java/com/vonage/client/messages/MessageRequestTest.java b/src/test/java/com/vonage/client/messages/MessageRequestTest.java index 556b800c6..b59ce2272 100644 --- a/src/test/java/com/vonage/client/messages/MessageRequestTest.java +++ b/src/test/java/com/vonage/client/messages/MessageRequestTest.java @@ -185,7 +185,7 @@ public void testToString() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends ConcreteMessageRequest { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; SelfRefrencing() { super(builder(MessageType.TEXT, Channel.SMS).from("447900000009").to("12002009000")); diff --git a/src/test/java/com/vonage/client/messages/MessageStatusTest.java b/src/test/java/com/vonage/client/messages/MessageStatusTest.java index 4bb81e894..a581ee215 100644 --- a/src/test/java/com/vonage/client/messages/MessageStatusTest.java +++ b/src/test/java/com/vonage/client/messages/MessageStatusTest.java @@ -171,7 +171,7 @@ public void testFromJsonInvalid() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends MessageStatus { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; } new SelfRefrencing().toJson(); } diff --git a/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java b/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java index 257a69bc8..6fa04bc47 100644 --- a/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java +++ b/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java @@ -144,7 +144,7 @@ public void testFromJsonEmpty() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends ContactsList { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; } new SelfRefrencing().toJson(); } diff --git a/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java b/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java index fb774d61b..08c747a71 100644 --- a/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java +++ b/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java @@ -74,7 +74,7 @@ public void testNameLength() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends CreateSubaccountRequest { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; SelfRefrencing(Builder builder) { super(builder); diff --git a/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java b/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java index 94752431b..efabd44ac 100644 --- a/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java +++ b/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java @@ -99,7 +99,7 @@ public void testFromJsonEmpty() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends AbstractTransfer { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; } new SelfRefrencing().toJson(); } diff --git a/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java b/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java index 69a471c3e..1a8eadcc6 100644 --- a/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java +++ b/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java @@ -70,7 +70,7 @@ public void testNameLength() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends UpdateSubaccountRequest { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; SelfRefrencing(Builder builder) { super(builder); diff --git a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java index 7be3c20d1..a474fe0d5 100644 --- a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java +++ b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java @@ -286,7 +286,7 @@ public void testFraudCheckOnlyIncludedIfFalse() { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends VerificationRequest { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; SelfRefrencing(Builder builder) { super(builder); diff --git a/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java b/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java index ef601e628..1a57bf125 100644 --- a/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java +++ b/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java @@ -117,7 +117,7 @@ public void testCustomUri() throws Exception { @Test(expected = VonageUnexpectedException.class) public void triggerJsonProcessingException() { class SelfRefrencing extends VerifyCodeRequestWrapper { - @JsonProperty("self") SelfRefrencing self = this; + @JsonProperty("self") final SelfRefrencing self = this; SelfRefrencing() { super(null, null); }