diff --git a/CHANGELOG.md b/CHANGELOG.md index defb3a133..8549d898a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +# [8.9.4] - 2024-07-25 +- Fixed UUID validation in `ConversationAction.Builder#canHear` and `canSpeak` + - Changed signature of `canHear` and `canSpeak` methods to return Strings instead of UUIDs +- Validation for `endpoint`, `limit` and `timeOut` in `ConnectAction` +- Added Builder to `DtmfSettings` with validation + # [8.9.3] - 2024-07-23 - Made `com.vonage.client.conversations.GenericEvent` public diff --git a/pom.xml b/pom.xml index e6bf008d7..daa75f9fa 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.vonage server-sdk - 8.9.3 + 8.9.4 Vonage Java Server SDK Java client for Vonage APIs diff --git a/src/main/java/com/vonage/client/HttpWrapper.java b/src/main/java/com/vonage/client/HttpWrapper.java index 80fb4b5c2..0ff46e157 100644 --- a/src/main/java/com/vonage/client/HttpWrapper.java +++ b/src/main/java/com/vonage/client/HttpWrapper.java @@ -34,7 +34,7 @@ public class HttpWrapper { private static final String CLIENT_NAME = "vonage-java-sdk", - CLIENT_VERSION = "8.9.3", + CLIENT_VERSION = "8.9.4", JAVA_VERSION = System.getProperty("java.version"), USER_AGENT = String.format("%s/%s java/%s", CLIENT_NAME, CLIENT_VERSION, JAVA_VERSION); diff --git a/src/main/java/com/vonage/client/voice/AdvancedMachineDetection.java b/src/main/java/com/vonage/client/voice/AdvancedMachineDetection.java index cabb27856..adb30b68e 100644 --- a/src/main/java/com/vonage/client/voice/AdvancedMachineDetection.java +++ b/src/main/java/com/vonage/client/voice/AdvancedMachineDetection.java @@ -124,6 +124,9 @@ public static Builder builder() { return new Builder(); } + /** + * Builder for specifying the Advanced Machine Detection properties. + */ public static class Builder { private MachineDetection behavior; private Mode mode; diff --git a/src/main/java/com/vonage/client/voice/ncco/AppEndpoint.java b/src/main/java/com/vonage/client/voice/ncco/AppEndpoint.java index e816b7c32..3de0d852e 100644 --- a/src/main/java/com/vonage/client/voice/ncco/AppEndpoint.java +++ b/src/main/java/com/vonage/client/voice/ncco/AppEndpoint.java @@ -68,6 +68,7 @@ public static class Builder { this.user = user; } + @Deprecated public Builder user(String user) { this.user = user; return this; diff --git a/src/main/java/com/vonage/client/voice/ncco/ConnectAction.java b/src/main/java/com/vonage/client/voice/ncco/ConnectAction.java index 6b9d62961..742ee7d40 100644 --- a/src/main/java/com/vonage/client/voice/ncco/ConnectAction.java +++ b/src/main/java/com/vonage/client/voice/ncco/ConnectAction.java @@ -43,19 +43,25 @@ public class ConnectAction extends JsonableBaseObject implements Action { ConnectAction() {} private ConnectAction(Builder builder) { - endpoint = builder.endpoint; + if ((endpoint = builder.endpoint) == null || endpoint.isEmpty()) { + throw new IllegalStateException("An endpoint must be specified."); + } + if ((limit = builder.limit) != null && (limit < 1 || limit > 7200)) { + throw new IllegalArgumentException("'limit' must be positive and less than 7200 seconds."); + } + if ((timeOut = builder.timeOut) != null && (timeOut < 3 || timeOut > 7200)) { + throw new IllegalArgumentException("'timeOut' must be between 3 and 7200 seconds."); + } from = builder.from; + if ((randomFromNumber = builder.randomFromNumber) != null && from != null) { + throw new IllegalStateException("'randomFromNumber' and 'from' cannot be used together."); + } eventType = builder.eventType; - timeOut = builder.timeOut; - limit = builder.limit; machineDetection = builder.machineDetection; advancedMachineDetection = builder.advancedMachineDetection; eventUrl = builder.eventUrl; eventMethod = builder.eventMethod; ringbackTone = builder.ringbackTone; - if ((randomFromNumber = builder.randomFromNumber) != null && from != null) { - throw new IllegalStateException("'randomFromNumber' and 'from' cannot be used together."); - } } @JsonProperty("action") @@ -163,7 +169,9 @@ public static class Builder { * @param endpoint The endpoints to connect to. * * @return This builder. + * @deprecated This will be removed in the next major release. */ + @Deprecated public Builder endpoint(Collection endpoint) { this.endpoint = endpoint; return this; @@ -213,8 +221,10 @@ public Builder eventType(EventType eventType) { } /** - * @param timeOut If the call is unanswered, set the number in seconds before Vonage stops ringing endpoint. - * The default value is 60. + * If the call is unanswered, set the number in seconds before Vonage stops ringing endpoint. + * The default value is 60, minimum is 3 and maximum is 7200 (2 hours). + * + * @param timeOut The call timeout in seconds. * * @return This builder. */ diff --git a/src/main/java/com/vonage/client/voice/ncco/ConversationAction.java b/src/main/java/com/vonage/client/voice/ncco/ConversationAction.java index 3130088d5..0109ec2ce 100644 --- a/src/main/java/com/vonage/client/voice/ncco/ConversationAction.java +++ b/src/main/java/com/vonage/client/voice/ncco/ConversationAction.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.vonage.client.JsonableBaseObject; import java.util.*; +import java.util.stream.Collectors; /** * An NCCO conversation action which enables the ability to host conference calls. @@ -28,8 +29,7 @@ public class ConversationAction extends JsonableBaseObject implements Action { private String name; private Boolean startOnEnter, endOnExit, record, mute; private EventMethod eventMethod; - private Collection musicOnHoldUrl, eventUrl; - private Collection canSpeak, canHear; + private Collection musicOnHoldUrl, eventUrl, canSpeak, canHear; private TranscriptionSettings transcription; ConversationAction() {} @@ -96,12 +96,12 @@ public EventMethod getEventMethod() { } @JsonProperty("canSpeak") - public Collection getCanSpeak() { + public Collection getCanSpeak() { return canSpeak; } @JsonProperty("canHear") - public Collection getCanHear() { + public Collection getCanHear() { return canHear; } @@ -121,12 +121,14 @@ public static Builder builder(String name) { return new Builder(name); } + /** + * Builder for specifying the properties of a Conversation NCCO. + */ public static class Builder { private String name; private EventMethod eventMethod; private Boolean startOnEnter, endOnExit, record, mute; - private Collection musicOnHoldUrl, eventUrl; - private Collection canSpeak, canHear; + private Collection musicOnHoldUrl, eventUrl, canSpeak, canHear; private TranscriptionSettings transcription; Builder(String name) { @@ -134,7 +136,9 @@ public static class Builder { } /** - * @param name The name of the Conversation room. + * Sets the name of the Conversation room. + * + * @param name The conversation name. * * @return This builder. */ @@ -150,17 +154,21 @@ public Builder name(String name) { * conversation, set startOnEnter to false for all users other than the moderator. * * @return This builder. + * @deprecated This will be removed in the next major release. */ + @Deprecated public Builder musicOnHoldUrl(Collection musicOnHoldUrl) { this.musicOnHoldUrl = musicOnHoldUrl; return this; } /** - * @param musicOnHoldUrl A URL to the mp3 file to stream to participants until the conversation starts. - * By default, the conversation starts when the first person calls the virtual number - * associated with your Voice app. To stream this mp3 before the moderator joins the - * conversation, set startOnEnter to false for all users other than the moderator. + * A URL to the mp3 file to stream to participants until the conversation starts. + * By default, the conversation starts when the first person calls the virtual number + * associated with your Voice app. To stream this mp3 before the moderator joins the + * conversation, set startOnEnter to false for all users other than the moderator. + * + * @param musicOnHoldUrl Absolute URL to the hold music in MP3 format, as a string. * * @return This builder. */ @@ -169,8 +177,10 @@ public Builder musicOnHoldUrl(String... musicOnHoldUrl) { } /** - * @param startOnEnter The default value of true ensures that the conversation starts when this caller joins - * conversation name. Set to false for attendees in a moderated conversation. + * The default value of {@code true} ensures that the conversation starts when this caller joins + * the conversation. Set to false for attendees in a moderated conversation. + * + * @param startOnEnter Whether to start the conversation when joining. * * @return This builder. */ @@ -180,10 +190,11 @@ public Builder startOnEnter(Boolean startOnEnter) { } /** - * @param endOnExit For moderated conversations, set to true in the moderator NCCO so the conversation is - * ended when the moderator hangs up. The default value of false means the conversation - * is not terminated when a caller hangs up; the conversation ends when the last caller - * hangs up. + * For moderated conversations, set to {@code true} in the moderator NCCO so the conversation is ended + * when the moderator hangs up. The default value of false means the conversation is not terminated + * when a caller hangs up; the conversation ends when the last caller hangs up. + * + * @param endOnExit Whether to end the conversation when the moderator hangs up. * * @return This builder. */ @@ -193,13 +204,17 @@ public Builder endOnExit(Boolean endOnExit) { } /** - * @param record Set to true to record this conversation. For standard conversations, recordings start when one - * or more attendees connects to the conversation. For moderated conversations, recordings start - * when the moderator joins. That is, when an NCCO is executed for the named conversation where - * startOnEnter is set to true. When the recording is terminated, the URL you download the - * recording from is sent to the event URL. - *

- * By default, audio is recorded in MP3 format. See the recording guide for more details + * Set to {@code true} to record this conversation. For standard conversations, recordings start + * when one or more attendees connects to the conversation. For moderated conversations, recordings + * start when the moderator joins. That is, when an NCCO is executed for the named conversation where + * startOnEnter is set to true. When the recording is terminated, the URL you download the recording + * from is sent to the event URL. + *

+ * By default, audio is recorded in MP3 format. See the + * recording guide + * for more details. + * + * @param record Whether to enable recording. * * @return This builder. */ @@ -213,15 +228,19 @@ public Builder record(Boolean record) { * Call States. * * @return This builder. + * @deprecated This will be removed in the next major release. */ + @Deprecated public Builder eventUrl(Collection eventUrl) { this.eventUrl = eventUrl; return this; } /** - * @param eventUrl Set the URL to the webhook endpoint Vonage calls asynchronously on each of the - * Call States. + * Set the URL to the webhook endpoint Vonage calls asynchronously on each of the + * Call States. + * + * @param eventUrl The event URL as a string. * * @return This builder. */ @@ -230,7 +249,9 @@ public Builder eventUrl(String... eventUrl) { } /** - * @param eventMethod Set the HTTP method used to make the request to eventUrl. The default value is POST. + * Set the HTTP method used to make the request to eventUrl. The default value is POST. + * + * @param eventMethod The event method as an enum. * * @return This builder. */ @@ -264,11 +285,11 @@ public Builder mute(boolean mute) { * @since 8.2.0 * @see #canSpeak(Collection) */ - public Builder addCanSpeak(String uuid) { + public Builder addCanSpeak(String... uuid) { if (canSpeak == null) { canSpeak = new LinkedHashSet<>(); } - canSpeak.add(UUID.fromString(uuid)); + canSpeak.addAll(Arrays.asList(uuid)); return this; } @@ -280,13 +301,12 @@ public Builder addCanSpeak(String uuid) { * * @return This builder. * @since 8.2.0 - * @see #canHear(Collection) */ - public Builder addCanHear(String uuid) { + public Builder addCanHear(String... uuid) { if (canHear == null) { - canHear = new LinkedHashSet<>(); + canHear = new LinkedHashSet<>(uuid.length); } - canHear.add(UUID.fromString(uuid)); + canHear.addAll(Arrays.asList(uuid)); return this; } @@ -299,15 +319,10 @@ public Builder addCanHear(String uuid) { * * @return This builder. * @since 8.2.0 - * @see #addCanSpeak(String) + * @see #addCanSpeak(String...) */ - public Builder canSpeak(Collection canSpeak) { - if (canSpeak == null) { - this.canSpeak = null; - } - else { - this.canSpeak = new LinkedHashSet<>(canSpeak); - } + public Builder canSpeak(Collection canSpeak) { + this.canSpeak = canSpeak; return this; } @@ -320,15 +335,10 @@ public Builder canSpeak(Collection canSpeak) { * * @return This builder. * @since 8.2.0 - * @see #addCanHear(String) + * @see #addCanHear(String...) */ - public Builder canHear(Collection canHear) { - if (canHear == null) { - this.canHear = null; - } - else { - this.canHear = new LinkedHashSet<>(canHear); - } + public Builder canHear(Collection canHear) { + this.canHear = canHear; return this; } @@ -353,10 +363,10 @@ public Builder transcription(TranscriptionSettings transcription) { */ public ConversationAction build() { if (canSpeak != null) { - canSpeak = new ArrayList<>(canSpeak); + canSpeak = canSpeak.stream().distinct().collect(Collectors.toList()); } if (canHear != null) { - canHear = new ArrayList<>(canHear); + canHear = canHear.stream().distinct().collect(Collectors.toList()); } return new ConversationAction(this); } diff --git a/src/main/java/com/vonage/client/voice/ncco/DtmfSettings.java b/src/main/java/com/vonage/client/voice/ncco/DtmfSettings.java index 8ac0e45d3..c90ff6f3c 100644 --- a/src/main/java/com/vonage/client/voice/ncco/DtmfSettings.java +++ b/src/main/java/com/vonage/client/voice/ncco/DtmfSettings.java @@ -18,47 +18,158 @@ import com.vonage.client.JsonableBaseObject; /** - * DTMF(Dial Tone Multi Frequency) settings for Input Actions that will be added to a NCCO object. + * DTMF (Dial Tone Multi Frequency) settings for Input Actions that will be added to a NCCO object. */ public class DtmfSettings extends JsonableBaseObject { private Integer timeOut, maxDigits; private Boolean submitOnHash; + /** + * Constructor. + * @deprecated Use {@link #builder()}. This will be made package-private in the next major release. + */ + @Deprecated + public DtmfSettings() {} + + private DtmfSettings(Builder builder) { + submitOnHash = builder.submitOnHash; + if ((timeOut = builder.timeOut) != null && (timeOut < 0 || timeOut > 10)) { + throw new IllegalArgumentException("'timeOut' must be positive and less than 10 seconds."); + } + if ((maxDigits = builder.maxDigits) != null && (maxDigits < 0 || maxDigits > 20)) { + throw new IllegalArgumentException("'maxDigits' must be positive and less than 20."); + } + } + + /** + * Time to wait in seconds before submitting the event. Default value is 3. + * + * @return The DTMF input timeout in seconds as an integer, or {@code null} if unspecified. + */ public Integer getTimeOut() { return timeOut; } /** - * @param timeOut The result of the callee's activity is sent to the eventUrl webhook endpoint timeOut seconds - * after the last action. The default value is 3. Max is 10. + * The result of the callee's activity is sent to the {@code eventUrl} webhook endpoint + * {@code timeOut} seconds after the last action. The default value is 3. Max is 10. + * + * @param timeOut The DTMF input timeout in seconds as an int. + * @deprecated Use the {@linkplain #builder()}. This will be removed in the next major release. */ + @Deprecated public void setTimeOut(Integer timeOut) { this.timeOut = timeOut; } - + /** + * The number of digits the user can press. The maximum value is 20, the default is 4 digits. + * + * @return The number of digits as an integer, or {@code null} if unspecified. + */ public Integer getMaxDigits() { return maxDigits; } /** - * @param maxDigits The number of digits the user can press. The maximum value is 20, the default is 4 digits. + * The number of digits the user can press. The maximum value is 20, the default is 4 digits. + * + * @param maxDigits The number of digits as an int. + * @deprecated Use the {@linkplain #builder()}. This will be removed in the next major release. */ + @Deprecated public void setMaxDigits(Integer maxDigits) { this.maxDigits = maxDigits; } + /** + * Determines if the callee's activity is sent to your webhook endpoint after pressing the hash key. + * + * @return {@code true} if the input is submitted on {@code #}, or {@code null} if unspecified. + */ public Boolean isSubmitOnHash() { return submitOnHash; } /** - * @param submitOnHash Set to true so the callee's activity is sent to your webhook endpoint at eventUrl after - * he or she presses #. If # is not pressed the result is submitted after timeOut seconds. - * The default value is false. That is, the result is sent to your webhook endpoint after - * timeOut seconds. + * Set to {@code true} so the callee's activity is sent to your webhook endpoint at {@code eventUrl} + * after they press {@code #}. If # is not pressed the result is submitted after {@code timeOut} seconds. + * The default value is {@code false}. That is, the result is sent to your webhook endpoint after + * {@code timeOut} seconds. + * + * @param submitOnHash Whether to submit the input after pressing the hash key. + * @deprecated Use the {@linkplain #builder()}. This will be removed in the next major release. */ + @Deprecated public void setSubmitOnHash(Boolean submitOnHash) { this.submitOnHash = submitOnHash; } + + /** + * Entrypoint for constructing an instance of this class. + * + * @return A new Builder. + * @since 8.9.4 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for specifying DTMF settings. + * + * @since 8.9.4 + */ + public static final class Builder { + private Integer timeOut, maxDigits; + private Boolean submitOnHash; + + private Builder() {} + + /** + * The result of the callee's activity is sent to the {@code eventUrl} webhook endpoint + * {@code timeOut} seconds after the last action. The default value is 3. Max is 10. + * + * @param timeOut The DTMF input timeout in seconds as an int. + * @return This builder. + */ + public Builder timeOut(int timeOut) { + this.timeOut = timeOut; + return this; + } + + /** + * The number of digits the user can press. The maximum value is 20, the default is 4 digits. + * + * @param maxDigits The number of digits as an int. + * @return This builder. + */ + public Builder maxDigits(int maxDigits) { + this.maxDigits = maxDigits; + return this; + } + + /** + * Set to {@code true} so the callee's activity is sent to your webhook endpoint at {@code eventUrl} + * after they press {@code #}. If # is not pressed the result is submitted after {@code timeOut} + * seconds. The default value is {@code false}. That is, the result is sent to your webhook + * endpoint after {@code timeOut} seconds. + * + * @param submitOnHash Whether to submit the input after pressing the hash key. + * @return This builder. + */ + public Builder submitOnHash(boolean submitOnHash) { + this.submitOnHash = submitOnHash; + return this; + } + + /** + * Builds the DtmfSettings with this builder's properties. + * + * @return A new DtmfSettings instance. + */ + public DtmfSettings build() { + return new DtmfSettings(this); + } + } } \ No newline at end of file diff --git a/src/main/java/com/vonage/client/voice/ncco/EventMethod.java b/src/main/java/com/vonage/client/voice/ncco/EventMethod.java index db3b5f372..386ec3de3 100644 --- a/src/main/java/com/vonage/client/voice/ncco/EventMethod.java +++ b/src/main/java/com/vonage/client/voice/ncco/EventMethod.java @@ -16,7 +16,7 @@ package com.vonage.client.voice.ncco; /** - * The type of request to use when connecting to an event url. + * The type of request to use when connecting to an event URL. */ public enum EventMethod { POST, GET diff --git a/src/main/java/com/vonage/client/voice/ncco/VbcEndpoint.java b/src/main/java/com/vonage/client/voice/ncco/VbcEndpoint.java index b9aa6cc42..83f7680e0 100644 --- a/src/main/java/com/vonage/client/voice/ncco/VbcEndpoint.java +++ b/src/main/java/com/vonage/client/voice/ncco/VbcEndpoint.java @@ -61,6 +61,9 @@ public static Builder builder(String extension) { return new Builder(extension); } + /** + * Builder for specifying properties of a VBC endpoint. + */ public static class Builder { private String extension; diff --git a/src/test/java/com/vonage/client/voice/CallTest.java b/src/test/java/com/vonage/client/voice/CallTest.java index 3e474fd92..3aeefb6cc 100644 --- a/src/test/java/com/vonage/client/voice/CallTest.java +++ b/src/test/java/com/vonage/client/voice/CallTest.java @@ -137,7 +137,7 @@ public void testFromJsonWithEveryActionAndEndpoint() { " {\n" + " \"action\":\"input\",\n" + " \"dtmf\":{\n" + - " \"timeOut\":30,\n" + + " \"timeOut\":8,\n" + " \"maxDigits\":12,\n" + " \"submitOnHash\":false\n" + " }\n" + @@ -201,7 +201,7 @@ public void testFromJsonWithEveryActionAndEndpoint() { InputAction input = (InputAction) actionsIter.next(); DtmfSettings dtmf = input.getDtmf(); - assertEquals(Integer.valueOf(30), dtmf.getTimeOut()); + assertEquals(Integer.valueOf(8), dtmf.getTimeOut()); assertEquals(Integer.valueOf(12), dtmf.getMaxDigits()); assertFalse(dtmf.isSubmitOnHash()); @@ -308,7 +308,7 @@ public void testConstructAllParams() { .ncco( TalkAction.builder("Hello").build(), RecordAction.builder().build(), - ConnectAction.builder().build() + ConnectAction.builder(com.vonage.client.voice.ncco.VbcEndpoint.builder("123").build()).build() ) .answerMethod(HttpMethod.POST).eventMethod(HttpMethod.POST) .eventUrl("https://example.com/voice/event") diff --git a/src/test/java/com/vonage/client/voice/ncco/ConnectActionTest.java b/src/test/java/com/vonage/client/voice/ncco/ConnectActionTest.java index d8b788f98..99bfab1e7 100644 --- a/src/test/java/com/vonage/client/voice/ncco/ConnectActionTest.java +++ b/src/test/java/com/vonage/client/voice/ncco/ConnectActionTest.java @@ -239,7 +239,7 @@ public void testEventMethod() { @Test public void testRandomFromNumber() { - ConnectAction.Builder builder = ConnectAction.builder(VbcEndpoint.builder("789").build()); + var builder = ConnectAction.builder(VbcEndpoint.builder("789").build()); ConnectAction connect = builder.randomFromNumber(true).build(); String expectedJson = "[{\"endpoint\":[{\"extension\":\"789\",\"type\":\"vbc\"}]," + "\"randomFromNumber\":true,\"action\":\"connect\"}]"; @@ -251,4 +251,29 @@ public void testRandomFromNumber() { expectedJson = expectedJson.replace("true", "false"); assertEquals(expectedJson, new Ncco(connect).toJson()); } + + @Test + public void testEndpointRequired() { + assertThrows(IllegalStateException.class, () -> ConnectAction.builder().build()); + } + + @Test + public void testLimitBoundaries() { + var builder = ConnectAction.builder(AppEndpoint.builder("Me").build()); + int max = 7200; + assertEquals(max, builder.limit(max).build().getLimit()); + assertThrows(IllegalArgumentException.class, () -> builder.limit(max + 1).build()); + assertEquals(1, builder.limit(1).build().getLimit()); + assertThrows(IllegalArgumentException.class, () -> builder.limit(0).build()); + } + + @Test + public void testTimeOutBoundaries() { + var builder = ConnectAction.builder(AppEndpoint.builder("Me").build()); + int min = 3, max = 7200; + assertEquals(max, builder.timeOut(max).build().getTimeOut()); + assertThrows(IllegalArgumentException.class, () -> builder.timeOut(max + 1).build()); + assertEquals(min, builder.timeOut(min).build().getTimeOut()); + assertThrows(IllegalArgumentException.class, () -> builder.timeOut(min - 1).build()); + } } diff --git a/src/test/java/com/vonage/client/voice/ncco/ConversationActionTest.java b/src/test/java/com/vonage/client/voice/ncco/ConversationActionTest.java index 8bea8ea1b..5f3a05537 100644 --- a/src/test/java/com/vonage/client/voice/ncco/ConversationActionTest.java +++ b/src/test/java/com/vonage/client/voice/ncco/ConversationActionTest.java @@ -118,12 +118,12 @@ public void testCanSpeak() { ConversationAction.Builder builder = newBuilder().addCanSpeak(uuid1).addCanSpeak(uuid2); ConversationAction conversation = builder.build(); - Collection canSpeak = conversation.getCanSpeak(); + var canSpeak = conversation.getCanSpeak(); assertNotNull(canSpeak); assertEquals(2, canSpeak.size()); - Iterator iter = canSpeak.iterator(); - assertEquals(uuid1, iter.next().toString()); - assertEquals(uuid2, iter.next().toString()); + Iterator iter = canSpeak.iterator(); + assertEquals(uuid1, iter.next()); + assertEquals(uuid2, iter.next()); assertTrue(conversation.toJson().contains("\"canSpeak\":[\""+uuid1+"\",\""+uuid2+"\"]")); conversation = builder.canSpeak(Collections.emptyList()).build(); @@ -155,12 +155,12 @@ public void testCanHear() { ConversationAction.Builder builder = newBuilder().addCanHear(uuid1).addCanHear(uuid2); ConversationAction conversation = builder.build(); - Collection canHear = conversation.getCanHear(); + var canHear = conversation.getCanHear(); assertNotNull(canHear); assertEquals(2, canHear.size()); - Iterator iter = canHear.iterator(); - assertEquals(uuid1, iter.next().toString()); - assertEquals(uuid2, iter.next().toString()); + Iterator iter = canHear.iterator(); + assertEquals(uuid1, iter.next()); + assertEquals(uuid2, iter.next()); assertTrue(conversation.toJson().contains("\"canHear\":[\""+uuid1+"\",\""+uuid2+"\"]")); conversation = builder.canHear(Collections.emptyList()).build(); diff --git a/src/test/java/com/vonage/client/voice/ncco/InputActionTest.java b/src/test/java/com/vonage/client/voice/ncco/InputActionTest.java index 21fef3be3..9c3121c91 100644 --- a/src/test/java/com/vonage/client/voice/ncco/InputActionTest.java +++ b/src/test/java/com/vonage/client/voice/ncco/InputActionTest.java @@ -16,6 +16,7 @@ package com.vonage.client.voice.ncco; import com.vonage.client.TestUtils; +import static com.vonage.client.TestUtils.testJsonableBaseObject; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; import java.util.Arrays; @@ -32,26 +33,20 @@ public void testBuilderMultipleInstances() { @Test public void testAllFields() { - SpeechSettings speechSettings = new SpeechSettings(); - speechSettings.setUuid(Collections.singletonList("aaaaaaaa-bbbb-cccc-dddd-0123456789ab")); - speechSettings.setStartTimeout(3); - speechSettings.setEndOnSilence(5); - speechSettings.setLanguage(SpeechSettings.Language.ENGLISH_NIGERIA); - speechSettings.setMaxDuration(50); - speechSettings.setContext(Arrays.asList("support", "buy", "credit")); - - DtmfSettings dtmfSettings = new DtmfSettings(); - dtmfSettings.setMaxDigits(4); - dtmfSettings.setSubmitOnHash(true); - dtmfSettings.setTimeOut(10); + var speechSettings = SpeechSettings.builder() + .uuid("aaaaaaaa-bbbb-cccc-dddd-0123456789ab") + .startTimeout(3).endOnSilence(5).maxDuration(50) + .language(SpeechSettings.Language.ENGLISH_NIGERIA) + .context(Arrays.asList("support", "buy", "credit")).build(); + + var dtmfSettings = DtmfSettings.builder().maxDigits(4).submitOnHash(true).timeOut(10).build(); InputAction input = InputAction.builder() .type(Arrays.asList("speech", "dtmf")) .dtmf(dtmfSettings) .eventUrl("http://example.com") .eventMethod(EventMethod.POST) - .speech(speechSettings) - .build(); + .speech(speechSettings).build(); String expectedJson = "[" + "{\"type\":[\"speech\",\"dtmf\"]," + @@ -92,14 +87,33 @@ public void testDefault() { @Test public void testDtmfOnly() { - DtmfSettings dtmfSettings = new DtmfSettings(); - dtmfSettings.setMaxDigits(4); + var dtmfSettings = DtmfSettings.builder().maxDigits(4).build(); InputAction input = InputAction.builder().type(Collections.singletonList("dtmf")).dtmf(dtmfSettings).build(); String expectedJson = "[{\"type\":[\"dtmf\"],\"dtmf\":{\"maxDigits\":4},\"action\":\"input\"}]"; assertEquals(expectedJson, new Ncco(input).toJson()); } + @Test + public void testDtmfSettingsBoundaries() { + var blank = DtmfSettings.builder().build(); + testJsonableBaseObject(blank); + assertNull(blank.isSubmitOnHash()); + assertNull(blank.getMaxDigits()); + assertNull(blank.getTimeOut()); + + int timeOutMin = 0, timeOutMax = 10, maxDigitsMin = 0, maxDigitsMax = 20; + assertEquals(timeOutMin, DtmfSettings.builder().timeOut(timeOutMin).build().getTimeOut()); + assertThrows(IllegalArgumentException.class, () -> DtmfSettings.builder().timeOut(timeOutMin - 1).build()); + assertEquals(timeOutMax, DtmfSettings.builder().timeOut(timeOutMax).build().getTimeOut()); + assertThrows(IllegalArgumentException.class, () -> DtmfSettings.builder().timeOut(timeOutMax + 1).build()); + + assertEquals(maxDigitsMin, DtmfSettings.builder().maxDigits(maxDigitsMin).build().getMaxDigits()); + assertThrows(IllegalArgumentException.class, () -> DtmfSettings.builder().maxDigits(maxDigitsMin - 1).build()); + assertEquals(maxDigitsMax, DtmfSettings.builder().maxDigits(maxDigitsMax).build().getMaxDigits()); + assertThrows(IllegalArgumentException.class, () -> DtmfSettings.builder().maxDigits(maxDigitsMax + 1).build()); + } + @Test public void testSpeechOnly() { String uuid = UUID.randomUUID().toString(); @@ -109,7 +123,7 @@ public void testSpeechOnly() { .context("hint1", "Hint 2").uuid(uuid) .saveAudio(true).maxDuration(60).build(); - TestUtils.testJsonableBaseObject(speechSettings); + testJsonableBaseObject(speechSettings); InputAction input = InputAction.builder().type(Collections.singletonList("speech")).speech(speechSettings).build(); String expectedJson = "[{\"type\":[\"speech\"],\"speech\":{\"uuid\":[\""+uuid+"\"],\"context\":" + "[\"hint1\",\"Hint 2\"],\"endOnSilence\":2.0,\"startTimeout\":10," + @@ -120,7 +134,7 @@ public void testSpeechOnly() { @Test public void testSpeechSettingsBoundaries() { - TestUtils.testJsonableBaseObject(SpeechSettings.builder().build()); + testJsonableBaseObject(SpeechSettings.builder().build()); assertNotNull(SpeechSettings.builder().maxDuration(1).build()); assertNotNull(SpeechSettings.builder().maxDuration(60).build()); diff --git a/src/test/java/com/vonage/client/voice/ncco/NccoTest.java b/src/test/java/com/vonage/client/voice/ncco/NccoTest.java index 11b9c5839..46ef69c4d 100644 --- a/src/test/java/com/vonage/client/voice/ncco/NccoTest.java +++ b/src/test/java/com/vonage/client/voice/ncco/NccoTest.java @@ -73,8 +73,7 @@ public void testGetActions() { @Test public void testSerializeMultipleActions() { TalkAction talk = TalkAction.builder("Test message").language(TextToSpeechLanguage.BASQUE).build(); - DtmfSettings dtmfSettings = new DtmfSettings(); - dtmfSettings.setMaxDigits(5); + var dtmfSettings = DtmfSettings.builder().maxDigits(5).build(); InputAction input = InputAction.builder().dtmf(dtmfSettings).type(Collections.singletonList("dtmf")).build(); RecordAction record = RecordAction.builder().beepStart(true).build(); ConnectAction connect = ConnectAction.builder(PhoneEndpoint.builder("15554441234").build()).build();