From fc884ba7ad2d50a98b7999f8d95be7a994e1636e Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Thu, 4 Jan 2024 13:31:53 +0000 Subject: [PATCH] feat: Add new properties to Messages API (#504) * Add webhook settings to MessageRequest * Add vCard caption * Add name for WhatsApp file * Added image caption to inbound * Add Whatsapp ContextStatus * Add inbound SMS status objects * Add ttl to outbound SMS * Add outbound SMS settings * Add inbound Whatsapp Referral * Add whatsapp obj to MessageStatus * Update changelog --- CHANGELOG.md | 9 ++ .../client/messages/InboundMessage.java | 60 ++++++++++-- .../client/messages/MessageRequest.java | 65 ++++++++++++ .../vonage/client/messages/MessageStatus.java | 98 ++++++++++++++++++- .../client/messages/MessagesVersion.java | 41 ++++++++ .../messages/internal/MessagePayload.java | 12 ++- .../client/messages/mms/MmsAudioRequest.java | 8 +- .../client/messages/mms/MmsImageRequest.java | 8 +- .../client/messages/mms/MmsRequest.java | 7 +- .../client/messages/mms/MmsVcardRequest.java | 15 ++- .../client/messages/mms/MmsVideoRequest.java | 8 +- .../client/messages/sms/EncodingType.java | 42 ++++++++ .../client/messages/sms/OutboundSettings.java | 64 ++++++++++++ .../client/messages/sms/SmsTextRequest.java | 83 +++++++++++++++- .../client/messages/whatsapp/Context.java | 3 + .../messages/whatsapp/ContextStatus.java | 45 +++++++++ .../messages/whatsapp/ConversationType.java | 45 +++++++++ .../client/messages/whatsapp/Referral.java | 87 ++++++++++++++++ .../whatsapp/WhatsappFileRequest.java | 20 +++- .../client/messages/InboundMessageTest.java | 46 +++++++-- .../client/messages/MessageRequestTest.java | 10 +- .../client/messages/MessageStatusTest.java | 54 +++++++--- .../messages/mms/MmsVcardRequestTest.java | 16 ++- .../messages/sms/SmsTextRequestTest.java | 89 ++++++++++++++--- .../whatsapp/WhatsappFileRequestTest.java | 17 ++-- 25 files changed, 883 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/vonage/client/messages/MessagesVersion.java create mode 100644 src/main/java/com/vonage/client/messages/sms/EncodingType.java create mode 100644 src/main/java/com/vonage/client/messages/sms/OutboundSettings.java create mode 100644 src/main/java/com/vonage/client/messages/whatsapp/ContextStatus.java create mode 100644 src/main/java/com/vonage/client/messages/whatsapp/ConversationType.java create mode 100644 src/main/java/com/vonage/client/messages/whatsapp/Referral.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f5f50a8..5a6a0c121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). # [8.1.0] - 2024-01-?? +- Added various missing fields in Messages API: + - `webhook_version` and `webhook_url` for all outbound messages + - MMS vCard `caption` (outbound) + - MMS image `caption` (inbound) + - Whatsapp file `name` (outbound) + - Whatsapp `context_status` and `referral` (inbound) + - SMS `count_total` and `network_code` (inbound) + - SMS `ttl`, `encoding_type`, `content_id` and `entity_id` (outbound) + - Whatsapp conversation type and ID (status update) - Added optional `from` parameter to Verify v2 SMS workflow - Fixed `length` not being set in `VerifyClient.verify` overload method - Fixed incorrect HTTP method for updating Video Broadcast layout diff --git a/src/main/java/com/vonage/client/messages/InboundMessage.java b/src/main/java/com/vonage/client/messages/InboundMessage.java index 23744ea56..3f590279b 100644 --- a/src/main/java/com/vonage/client/messages/InboundMessage.java +++ b/src/main/java/com/vonage/client/messages/InboundMessage.java @@ -15,10 +15,7 @@ */ package com.vonage.client.messages; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.*; import com.vonage.client.Jsonable; import com.vonage.client.messages.sms.SmsInboundMetadata; import com.vonage.client.messages.whatsapp.*; @@ -28,7 +25,7 @@ import java.util.UUID; /** - * Convenience class representing an inbound message webhook. + * Convenience class representing an inbound message webhook. This maps all known fields for all message types. *

* Refer to the * Messages API Webhook reference @@ -43,10 +40,22 @@ protected static class UrlWrapper { @JsonProperty("url") protected URI url; } + @JsonIgnoreProperties(ignoreUnknown = true) + protected static class UrlWrapperWithCaption extends UrlWrapper { + @JsonProperty("caption") protected String caption; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + protected static class Whatsapp { + @JsonProperty("referral") protected Referral referral; + } + protected InboundMessage() {} @JsonAnySetter protected Map unknownProperties; + @JsonProperty("whatsapp") private Whatsapp whatsapp; + @JsonProperty("timestamp") protected Instant timestamp; @JsonProperty("channel") protected Channel channel; @JsonProperty("message_type") protected MessageType messageType; @@ -57,7 +66,7 @@ protected InboundMessage() {} @JsonProperty("provider_message") String providerMessage; @JsonProperty("text") protected String text; - @JsonProperty("image") protected UrlWrapper image; + @JsonProperty("image") protected UrlWrapperWithCaption image; @JsonProperty("audio") protected UrlWrapper audio; @JsonProperty("video") protected UrlWrapper video; @JsonProperty("file") protected UrlWrapper file; @@ -65,6 +74,7 @@ protected InboundMessage() {} @JsonProperty("sticker") protected UrlWrapper sticker; @JsonProperty("profile") protected Profile whatsappProfile; + @JsonProperty("context_status") protected ContextStatus whatsappContextStatus; @JsonProperty("context") protected Context whatsappContext; @JsonProperty("location") protected Location whatsappLocation; @JsonProperty("reply") protected Reply whatsappReply; @@ -164,6 +174,17 @@ public URI getImageUrl() { return image != null ? image.url : null; } + /** + * Additional text accompanying the image. Applicable to MMS image messages only. + * + * @return The image caption if present, or {@code null} if not applicable. + * + * @since 8.1.0 + */ + public String getImageCaption() { + return image != null ? image.caption : null; + } + /** * If {@linkplain #getMessageType()} is {@linkplain MessageType#AUDIO}, returns the URL of the audio. * @@ -270,6 +291,20 @@ public Context getWhatsappContext() { return whatsappContext; } + /** + * If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP}, returns an enum indicating whether there + * is a context for this inbound message. If there is a context, and it is available, the context details will be + * contained in a context object. If there is a context, but it is unavailable,or if there is no context for + * message ({@linkplain ContextStatus#NONE}), then there will be no context object included in the body. + * + * @return The deserialized WhatsApp context status, or {@code null} if not applicable. + * + * @since 8.1.0 + */ + public ContextStatus getWhatsappContextStatus() { + return whatsappContextStatus; + } + /** * If the {@linkplain #getChannel()} is {@linkplain Channel#SMS}, returns the usage * information (charged incurred for the message). @@ -290,6 +325,19 @@ public SmsInboundMetadata getSmsMetadata() { return smsMetadata; } + /** + * If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and a content referral is present in + * the message, returns the metadata related to the post or advertisement that the user clicked on. + * + * @return The Whatsapp referral object, or {@code null} if not present or applicable. + * + * @since 8.1.0 + */ + @JsonIgnore + public Referral getWhatsappReferral() { + return whatsapp != null ? whatsapp.referral : null; + } + /** * Constructs an instance of this class from a JSON payload. Known fields will be mapped, whilst * unknown fields can be manually obtained through the {@linkplain #getUnmappedProperties()} method. diff --git a/src/main/java/com/vonage/client/messages/MessageRequest.java b/src/main/java/com/vonage/client/messages/MessageRequest.java index 88c3ce62a..515738300 100644 --- a/src/main/java/com/vonage/client/messages/MessageRequest.java +++ b/src/main/java/com/vonage/client/messages/MessageRequest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.vonage.client.Jsonable; import com.vonage.client.common.E164; +import java.net.URI; import java.util.Objects; /** @@ -36,6 +37,8 @@ public abstract class MessageRequest implements Jsonable { final MessageType messageType; final Channel channel; final String clientRef; + final URI webhookUrl; + final MessagesVersion webhookVersion; protected String from, to; /** @@ -57,6 +60,8 @@ protected MessageRequest(Builder builder, Channel channel, MessageType mes clientRef = validateClientReference(builder.clientRef); from = builder.from; to = builder.to; + webhookUrl = builder.webhookUrl; + webhookVersion = builder.webhookVersion; validateSenderAndRecipient(from, to); } @@ -115,6 +120,16 @@ public String getClientRef() { return clientRef; } + @JsonProperty("webhook_url") + public URI getWebhookUrl() { + return webhookUrl; + } + + @JsonProperty("webhook_version") + public MessagesVersion getWebhookVersion() { + return webhookVersion; + } + @Override public String toString() { return getClass().getSimpleName()+' '+toJson(); @@ -133,6 +148,8 @@ public String toString() { @SuppressWarnings("unchecked") public abstract static class Builder> { protected String from, to, clientRef; + protected URI webhookUrl; + protected MessagesVersion webhookVersion; /** * Protected constructor to prevent users from explicitly creating this object. @@ -178,6 +195,54 @@ public B clientRef(String clientRef) { return (B) this; } + /** + * (OPTIONAL) + * Specifies the URL to which Status Webhook messages will be sent for this particular message. + * Overrides account-level and application-level Status Webhook url settings on a per-message basis. + * + * @param webhookUrl The status webhook URL as a string. + * + * @return This builder. + * + * @since 8.1.0 + */ + public B webhookUrl(String webhookUrl) { + return webhookUrl(URI.create(webhookUrl)); + } + + /** + * (OPTIONAL) + * Specifies the URL to which Status Webhook messages will be sent for this particular message. + * Overrides account-level and application-level Status Webhook url settings on a per-message basis. + * + * @param webhookUrl The status webhook URL. + * + * @return This builder. + * + * @since 8.1.0 + */ + private B webhookUrl(URI webhookUrl) { + this.webhookUrl = webhookUrl; + return (B) this; + } + + /** + * Specifies which version of the Messages API will be used to send Status Webhook messages for + * this particular message. For example, if {@linkplain MessagesVersion#V0_1} is set, then the + * JSON body of Status Webhook messages for this message will be sent in Messages v0.1 format. + * Over-rides account-level and application-level API version settings on a per-message basis. + * + * @param webhookVersion The messages API version enum. + * + * @return This builder. + * + * @since 8.1.0 + */ + public B webhookVersion(MessagesVersion webhookVersion) { + this.webhookVersion = webhookVersion; + return (B) this; + } + /** * Builds the MessageRequest. * diff --git a/src/main/java/com/vonage/client/messages/MessageStatus.java b/src/main/java/com/vonage/client/messages/MessageStatus.java index 549884bf8..6e761e296 100644 --- a/src/main/java/com/vonage/client/messages/MessageStatus.java +++ b/src/main/java/com/vonage/client/messages/MessageStatus.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.*; import com.vonage.client.Jsonable; +import com.vonage.client.messages.whatsapp.ConversationType; import java.net.URI; import java.time.Instant; import java.util.Currency; @@ -186,10 +187,36 @@ public String toString() { } } + @JsonInclude(value = JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + static class Destination { + @JsonProperty("network_code") String networkCode; + } + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + static class Sms { + @JsonProperty("count_total") Integer countTotal; + } + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + static class Whatsapp { + static class Conversation { + static class Origin { + @JsonProperty("type") ConversationType type; + } + @JsonProperty("id") String id; + @JsonProperty("origin") Origin origin; + } + @JsonProperty("conversation") Conversation conversation; + } + protected MessageStatus() { } @JsonAnySetter protected Map unknownProperties; + @JsonProperty("timestamp") protected Instant timestamp; @JsonProperty("message_uuid") protected UUID messageUuid; @JsonProperty("to") protected String to; @@ -200,6 +227,10 @@ protected MessageStatus() { @JsonProperty("error") protected Error error; @JsonProperty("usage") protected Usage usage; + @JsonProperty("destination") private Destination destination; + @JsonProperty("sms") private Sms sms; + @JsonProperty("whatsapp") private Whatsapp whatsapp; + /** * Unique identifier of the message that was sent, as returned in {@link MessageResponse#getMessageUuid()}. @@ -283,6 +314,62 @@ public Usage getUsage() { return usage; } + /** + * If {@linkplain #getChannel()} is {@linkplain Channel#SMS} or {@linkplain Channel#MMS}, + * returns the network code for the destination. + * + * @return The mobile network code as a string, or {@code null} if not applicable. + * + * @since 8.1.0 + */ + @JsonIgnore + public String getDestinationNetworkCode() { + return destination != null ? destination.networkCode : null; + } + + /** + * {@linkplain #getChannel()} is {@linkplain Channel#SMS}, returns the number of SMS messages concatenated together + * to comprise the submitted message. SMS messages are 160 characters, if a submitted message exceeds that size it + * is sent as multiple SMS messages. This number indicates how many SMS messages are required. + * + * @return The number of SMS messages used for this message, or {@code null} if not applicable. + * + * @since 8.1.0 + */ + @JsonIgnore + public Integer getSmsTotalCount() { + return sms != null ? sms.countTotal : null; + } + + /** + * If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and {@linkplain #getStatus()} is + * {@linkplain Status#DELIVERED}, returns the conversation's origin type. + * + * @return The WhatsApp conversation category as an enum, {@code null} if absent or not applicable. + * + * @since 8.1.0 + */ + @JsonIgnore + public ConversationType getWhatsappConversationType() { + return whatsapp != null && + whatsapp.conversation != null && + whatsapp.conversation.origin != null ? + whatsapp.conversation.origin.type : null; + } + + /** + * If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and {@linkplain #getStatus()} is + * {@linkplain Status#DELIVERED}, returns the conversation ID of the message that triggered this callback. + * + * @return The WhatsApp conversation ID, {@code null} if absent or not applicable. + * + * @since 8.1.0 + */ + @JsonIgnore + public String getWhatsappConversationId() { + return whatsapp != null && whatsapp.conversation != null ? whatsapp.conversation.id : null; + } + /** * Catch-all for properties which are not mapped by this class during deserialization. * @@ -318,11 +405,18 @@ public boolean equals(Object o) { Objects.equals(to, that.to) && Objects.equals(from, that.from) && status == that.status && channel == that.channel && Objects.equals(clientRef, that.clientRef) && - Objects.equals(error, that.error) && Objects.equals(usage, that.usage); + Objects.equals(error, that.error) && Objects.equals(usage, that.usage) && + Objects.equals(getDestinationNetworkCode(), that.getDestinationNetworkCode()) && + Objects.equals(getSmsTotalCount(), that.getSmsTotalCount()) && + Objects.equals(getWhatsappConversationId(), that.getWhatsappConversationId()) && + Objects.equals(getWhatsappConversationType(), that.getWhatsappConversationType()); } @Override public int hashCode() { - return Objects.hash(timestamp, messageUuid, to, from, status, channel, clientRef, error, usage); + return Objects.hash( + timestamp, messageUuid, to, from, status, channel, + clientRef, error, usage, getDestinationNetworkCode(), getSmsTotalCount() + ); } } diff --git a/src/main/java/com/vonage/client/messages/MessagesVersion.java b/src/main/java/com/vonage/client/messages/MessagesVersion.java new file mode 100644 index 000000000..7aaf63aca --- /dev/null +++ b/src/main/java/com/vonage/client/messages/MessagesVersion.java @@ -0,0 +1,41 @@ +/* + * 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.messages; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the possible versions used for webhooks in the Messages API. + * + * @since 8.1.0 + */ +public enum MessagesVersion { + /** + * v0.1 + */ + V0_1, + + /** + * v1 + */ + V1; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase().replace('_', '.'); + } +} diff --git a/src/main/java/com/vonage/client/messages/internal/MessagePayload.java b/src/main/java/com/vonage/client/messages/internal/MessagePayload.java index 80463459c..050a739fa 100644 --- a/src/main/java/com/vonage/client/messages/internal/MessagePayload.java +++ b/src/main/java/com/vonage/client/messages/internal/MessagePayload.java @@ -29,7 +29,7 @@ @JsonInclude(value = JsonInclude.Include.NON_NULL) public class MessagePayload { protected URI url; - protected String caption; + protected String caption, name; public MessagePayload(String url) { this.url = URI.create(url); @@ -42,6 +42,11 @@ public MessagePayload(String url, String caption) { } } + public MessagePayload(String url, String caption, String name) { + this(url, caption); + this.name = name; + } + @JsonProperty("url") public URI getUrl() { return url; @@ -52,6 +57,11 @@ public String getCaption() { return caption; } + @JsonProperty("name") + public String getName() { + return name; + } + public static void validateExtension(String path, String... allowed) { int lastDot = path.lastIndexOf('.'); if (lastDot < 1) return; diff --git a/src/main/java/com/vonage/client/messages/mms/MmsAudioRequest.java b/src/main/java/com/vonage/client/messages/mms/MmsAudioRequest.java index fe83ccd02..437dac840 100644 --- a/src/main/java/com/vonage/client/messages/mms/MmsAudioRequest.java +++ b/src/main/java/com/vonage/client/messages/mms/MmsAudioRequest.java @@ -39,8 +39,6 @@ public static Builder builder() { } public static final class Builder extends MmsRequest.Builder { - String caption; - Builder() {} /** @@ -50,6 +48,7 @@ public static final class Builder extends MmsRequest.Builder { - String caption; - Builder() {} /** @@ -52,6 +50,7 @@ public static final class Builder extends MmsRequest.Builder builder, MessageType messageType) { @SuppressWarnings("unchecked") protected abstract static class Builder> extends MessageRequest.Builder { - String url; + String url, caption; protected B url(String url) { this.url = url; return (B) this; } + + protected B caption(String caption) { + this.caption = caption; + return (B) this; + } } } diff --git a/src/main/java/com/vonage/client/messages/mms/MmsVcardRequest.java b/src/main/java/com/vonage/client/messages/mms/MmsVcardRequest.java index 40a2b2450..7e66487af 100644 --- a/src/main/java/com/vonage/client/messages/mms/MmsVcardRequest.java +++ b/src/main/java/com/vonage/client/messages/mms/MmsVcardRequest.java @@ -25,7 +25,7 @@ public final class MmsVcardRequest extends MmsRequest { MmsVcardRequest(Builder builder) { super(builder, MessageType.VCARD); - payload = new MessagePayload(builder.url); + payload = new MessagePayload(builder.url, builder.caption); payload.validateUrlExtension("vcf"); } @@ -52,10 +52,21 @@ public Builder url(String url) { return super.url(url); } + /** + * (OPTIONAL) + * Additional text to accompany the vCard. Must be between 1 and 2000 characters. + * + * @param caption The caption string. + * @return This builder. + */ + @Override + public Builder caption(String caption) { + return super.caption(caption); + } + @Override public MmsVcardRequest build() { return new MmsVcardRequest(this); } } - } diff --git a/src/main/java/com/vonage/client/messages/mms/MmsVideoRequest.java b/src/main/java/com/vonage/client/messages/mms/MmsVideoRequest.java index abbfe5c1e..0787febcd 100644 --- a/src/main/java/com/vonage/client/messages/mms/MmsVideoRequest.java +++ b/src/main/java/com/vonage/client/messages/mms/MmsVideoRequest.java @@ -39,8 +39,6 @@ public static Builder builder() { } public static final class Builder extends MmsRequest.Builder { - String caption; - Builder() {} /** @@ -50,6 +48,7 @@ public static final class Builder extends MmsRequest.Builder { - String text; + String text, contentId, entityId; + Integer ttl; + EncodingType encodingType; Builder() {} @@ -60,6 +78,69 @@ public Builder text(String text) { return this; } + /** + * (OPTIONAL) + * The duration in milliseconds the delivery of an SMS will be attempted. By default, Vonage attempts + * delivery for 72 hours, however the maximum effective value depends on the operator and is typically + * 24 to 48 hours. We recommend this value should be kept at its default or at least 30 minutes. + * + * @param ttl The time-to-live for this message before abandoning delivery attempts, in milliseconds. + * @return This builder. + * + * @since 8.1.0 + */ + public Builder ttl(int ttl) { + this.ttl = ttl; + return this; + } + + /** + * (OPTIONAL) + * The encoding type to use for the message. If set to either {@linkplain EncodingType#TEXT} or + * {@linkplain EncodingType#UNICODE}, the specified type will be used. If set to + * {@linkplain EncodingType#AUTO} (the default), the Messages API will automatically set the type based + * on the content of text; i.e. if unicode characters are detected in text, then the message will be + * encoded as unicode, and otherwise as text. + * + * @param encodingType The message encoding type as an enum. + * @return This builder. + * + * @since 8.1.0 + */ + public Builder encodingType(EncodingType encodingType) { + this.encodingType = encodingType; + return this; + } + + /** + * (OPTIONAL) + * A string parameter that satisfies regulatory requirements when sending an SMS to specific countries. + * + * @param contentId The content ID as a string. + * @return This builder. + * + * @since 8.1.0 + */ + public Builder contentId(String contentId) { + this.contentId = contentId; + return this; + } + + /** + * (OPTIONAL) + * A string parameter that satisfies regulatory requirements when sending an SMS to specific countries. + * + * @param entityId The entity ID as a string. + * @return This builder. + * + * @since 8.1.0 + */ + public Builder entityId(String entityId) { + this.entityId = entityId; + return this; + } + + @Override public SmsTextRequest build() { return new SmsTextRequest(this); } diff --git a/src/main/java/com/vonage/client/messages/whatsapp/Context.java b/src/main/java/com/vonage/client/messages/whatsapp/Context.java index 169f90ab6..0cbce0b8f 100644 --- a/src/main/java/com/vonage/client/messages/whatsapp/Context.java +++ b/src/main/java/com/vonage/client/messages/whatsapp/Context.java @@ -57,7 +57,10 @@ public UUID getMessageUuid() { * Only applies to Order messages. * * @return The referred product details, or {@code null} if not applicable. + * + * @deprecated This will be moved in a future release. */ + @Deprecated @JsonProperty("whatsapp_referred_product") public ReferredProduct getReferredProduct() { return referredProduct; diff --git a/src/main/java/com/vonage/client/messages/whatsapp/ContextStatus.java b/src/main/java/com/vonage/client/messages/whatsapp/ContextStatus.java new file mode 100644 index 000000000..02517ee66 --- /dev/null +++ b/src/main/java/com/vonage/client/messages/whatsapp/ContextStatus.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.messages.whatsapp; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +/** + * Represents the WhatsApp {@code context_status} field in {@link com.vonage.client.messages.InboundMessage}. + *

+ * Describes whether there is a context for this inbound message or not. If there is a context, and it is available, + * the context details will be contained in a context object. If there is a context, but it is unavailable, + * or if there is no context for message (none), then there will be no context object included in the body. + * + * @since 8.1.0 + */ +public enum ContextStatus { + NONE, + AVAILABLE, + UNAVAILABLE; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } + + @JsonCreator + public static ContextStatus fromString(String value) { + if (value == null || value.trim().isEmpty()) return null; + return ContextStatus.valueOf(value.toUpperCase()); + } +} diff --git a/src/main/java/com/vonage/client/messages/whatsapp/ConversationType.java b/src/main/java/com/vonage/client/messages/whatsapp/ConversationType.java new file mode 100644 index 000000000..a4ad5884e --- /dev/null +++ b/src/main/java/com/vonage/client/messages/whatsapp/ConversationType.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.messages.whatsapp; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.vonage.client.messages.MessageStatus; + +/** + * Represents the conversation category as returned by {@link MessageStatus#getWhatsappConversationType()}. + * + * @since 8.1.0 + */ +public enum ConversationType { + MARKETING, + UTILITY, + AUTHENTICATION, + REFERRAL_CONVERSION, + SERVICE; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } + + @JsonCreator + public static ConversationType fromString(String value) { + if (value == null || value.trim().isEmpty()) return null; + return ConversationType.valueOf(value.toUpperCase()); + } +} diff --git a/src/main/java/com/vonage/client/messages/whatsapp/Referral.java b/src/main/java/com/vonage/client/messages/whatsapp/Referral.java new file mode 100644 index 000000000..d384155ba --- /dev/null +++ b/src/main/java/com/vonage/client/messages/whatsapp/Referral.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.messages.whatsapp; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.messages.InboundMessage; +import java.net.URI; + +/** + * This is only present for situations where a user has clicked on a 'WhatsApp' button embedded in an advertisement + * or post on Facebook. Clicking on the button directs the user to the WhatsApp app from where they can send a message. + * The inbound message will contain this object which includes details of the Facebook advertisement or post which + * contained the embedded button. This is used in {@link InboundMessage#getWhatsappReferral()}. + * + * @since 8.1.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class Referral { + private String body, headline, sourceId, sourceType; + private URI sourceUrl; + + Referral() {} + + /** + * Body text of the referring advertisement or post. + * + * @return The referral body. + */ + @JsonProperty("body") + public String getBody() { + return body; + } + + /** + * Headline text of the referring advertisement or post. + * + * @return The referral headline. + */ + @JsonProperty("headline") + public String getHeadline() { + return headline; + } + + /** + * Meta/WhatsApp ID of the referring advertisement or post. + * + * @return The source referral ID as a String. + */ + @JsonProperty("source_id") + public String getSourceId() { + return sourceId; + } + + /** + * The type of the referring advertisement or post. + * + * @return The source referral type as a String. + */ + @JsonProperty("source_type") + public String getSourceType() { + return sourceType; + } + + /** + * A URL referencing the content of the media shown in the advertisement when the user clicked to send a message. + * + * @return Link to the advertised content. + */ + @JsonProperty("source_url") + public URI getSourceUrl() { + return sourceUrl; + } +} diff --git a/src/main/java/com/vonage/client/messages/whatsapp/WhatsappFileRequest.java b/src/main/java/com/vonage/client/messages/whatsapp/WhatsappFileRequest.java index deebad5f6..5f838c65b 100644 --- a/src/main/java/com/vonage/client/messages/whatsapp/WhatsappFileRequest.java +++ b/src/main/java/com/vonage/client/messages/whatsapp/WhatsappFileRequest.java @@ -26,7 +26,7 @@ public final class WhatsappFileRequest extends WhatsappRequest { WhatsappFileRequest(Builder builder) { super(builder, MessageType.FILE); - file = new MessagePayload(builder.url, builder.caption); + file = new MessagePayload(builder.url, builder.caption, builder.name); } @JsonProperty("file") @@ -39,7 +39,7 @@ public static Builder builder() { } public static final class Builder extends WhatsappRequest.Builder { - String url, caption; + String url, caption, name; Builder() {} @@ -68,10 +68,24 @@ public Builder caption(String caption) { return this; } + /** + * (OPTIONAL) + * Specifies the name of the file being sent. If not included, the value for caption will be used as + * the file name. If neither name nor caption are included, the file name will be parsed from the url. + * + * @param name The file name. + * @return This builder. + * + * @since 8.1.0 + */ + public Builder name(String name) { + this.name = name; + return this; + } + @Override public WhatsappFileRequest build() { return new WhatsappFileRequest(this); } } - } diff --git a/src/test/java/com/vonage/client/messages/InboundMessageTest.java b/src/test/java/com/vonage/client/messages/InboundMessageTest.java index ffce35dbf..97a51b877 100644 --- a/src/test/java/com/vonage/client/messages/InboundMessageTest.java +++ b/src/test/java/com/vonage/client/messages/InboundMessageTest.java @@ -17,9 +17,10 @@ import com.vonage.client.VonageResponseParseException; import com.vonage.client.messages.sms.SmsInboundMetadata; +import com.vonage.client.messages.whatsapp.Order; import com.vonage.client.messages.whatsapp.*; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.*; import java.net.URI; import java.time.Instant; import java.util.Currency; @@ -91,6 +92,11 @@ void assertEqualsSmsWithUsageAndMetadata(InboundMessage im) { assertEquals("HELLO", metadata.getKeyword()); } + @Test + public void testFromJsonInvalid() { + assertThrows(VonageResponseParseException.class, () -> InboundMessage.fromJson("{malformed]")); + } + @SuppressWarnings("unchecked") @Test public void testUnknownProperty() { @@ -175,9 +181,11 @@ public void testMmsVcard() { @Test public void testImageOnly() { URI image = URI.create("https://www.example.org/path/to/image.png"); - String json = "{\"image\": {\"url\":\""+image+"\"}}"; + String caption = "Alt text accompanying the image"; + String json = "{\"image\": {\"url\":\""+image+"\",\"caption\":\""+caption+"\"}}"; InboundMessage im = InboundMessage.fromJson(json); assertEquals(image, im.getImageUrl()); + assertEquals(caption, im.getImageCaption()); } @Test @@ -229,7 +237,7 @@ public void testWhatsappLocationOnly() { @Test public void testWhatsappReplyOnly() { - String id = "row1", title = "9am", description = "Select 9am appointmaent time"; + String id = "row1", title = "9am", description = "Select 9am appointment time"; String json = "{\"reply\":{\"id\":\""+id+"\",\"title\":\""+title+"\",\"description\":\""+description+"\"}}"; InboundMessage im = InboundMessage.fromJson(json); Reply reply = im.getWhatsappReply(); @@ -274,6 +282,7 @@ public void testWhatsappOrderOnly() { public void testWhatsappContextForOrderOnly() { UUID messageId = UUID.randomUUID(); String cid = "1267260820787549", pid = "r07qei73l7", from = "447700900001", json = "{\n" + + " \"context_status\": \"available\",\n" + " \"context\": {\n" + " \"whatsapp_referred_product\": {\n" + " \"catalog_id\": \""+cid+"\",\n" + @@ -284,6 +293,7 @@ public void testWhatsappContextForOrderOnly() { " }" + "}"; InboundMessage im = InboundMessage.fromJson(json); + assertEquals(ContextStatus.AVAILABLE, im.getWhatsappContextStatus()); Context context = im.getWhatsappContext(); assertNotNull(context); assertEquals(messageId, context.getMessageUuid()); @@ -295,10 +305,11 @@ public void testWhatsappContextForOrderOnly() { } @Test - public void testWhatsappContextAndProfileOnly() { + public void testWhatsappStatusContextAndProfileOnly() { UUID messageId = UUID.randomUUID(); String name = "Jane Smith", from = "447700900000", json = "{\n" + " \"profile\":{\"name\": \""+name+"\"},\n" + + " \"context_status\": \"unavailable\",\n" + " \"context\": {\n" + " \"message_uuid\": \""+messageId+"\",\n" + " \"message_from\": \""+from+"\"\n" + @@ -308,6 +319,7 @@ public void testWhatsappContextAndProfileOnly() { Profile profile = im.getWhatsappProfile(); assertNotNull(profile); assertEquals(name, profile.getName()); + assertEquals(ContextStatus.UNAVAILABLE.toString(), im.getWhatsappContextStatus().toString()); Context context = im.getWhatsappContext(); assertNotNull(context); assertEquals(messageId, context.getMessageUuid()); @@ -315,7 +327,29 @@ public void testWhatsappContextAndProfileOnly() { } @Test - public void testFromJsonInvalid() { - assertThrows(VonageResponseParseException.class, () -> InboundMessage.fromJson("{malformed]")); + public void testWhatsappReferralOnly() { + String body = "Check out our new product offering", + headline = "New Products!", sourceId = "212731241638144", + sourceType = "post", sourceUrl = "https://fb.me/2ZulEu42P", + json = "{\n" + + " \"whatsapp\": {\n" + + " \"referral\": {\n" + + " \"body\": \""+body+"\",\n" + + " \"headline\": \""+headline+"\",\n" + + " \"source_id\": \""+sourceId+"\",\n" + + " \"source_type\": \""+sourceType+"\",\n" + + " \"source_url\": \""+sourceUrl+"\"\n" + + " }\n" + + " }\n" + + "}"; + + InboundMessage im = InboundMessage.fromJson(json); + Referral referral = im.getWhatsappReferral(); + assertNotNull(referral); + assertEquals(body, referral.getBody()); + assertEquals(headline, referral.getHeadline()); + assertEquals(sourceId, referral.getSourceId()); + assertEquals(sourceType, referral.getSourceType()); + assertEquals(URI.create(sourceUrl), referral.getSourceUrl()); } } diff --git a/src/test/java/com/vonage/client/messages/MessageRequestTest.java b/src/test/java/com/vonage/client/messages/MessageRequestTest.java index 497c36912..c7fc2fc68 100644 --- a/src/test/java/com/vonage/client/messages/MessageRequestTest.java +++ b/src/test/java/com/vonage/client/messages/MessageRequestTest.java @@ -52,10 +52,14 @@ private ConcreteMessageRequest(Builder builder) { public void testSerializeAllFields() { MessageRequest smr = ConcreteMessageRequest.builder(MessageType.VIDEO, Channel.MMS) .from("447900000009").to("12002009000") - .clientRef("<40 character string").build(); + .clientRef("<40 character string") + .webhookUrl("https://example.com/status") + .webhookVersion(MessagesVersion.V1).build(); String generatedJson = smr.toJson(); assertTrue(generatedJson.contains("\"client_ref\":\"<40 character string\"")); + assertTrue(generatedJson.contains("\"webhook_url\":\"https://example.com/status\"")); + assertTrue(generatedJson.contains("\"webhook_version\":\"v1\"")); assertTrue(generatedJson.contains("\"from\":\"447900000009\"")); assertTrue(generatedJson.contains("\"to\":\"12002009000\"")); assertTrue(generatedJson.contains("\"channel\":\"mms\"")); @@ -63,12 +67,14 @@ public void testSerializeAllFields() { } @Test - public void testSerializeFieldsWithoutClientRef() { + public void testSerializeFieldsRequiredOnly() { MessageRequest smr = ConcreteMessageRequest.builder(MessageType.IMAGE, Channel.VIBER) .from("447900000009").to("12002009000").build(); String generatedJson = smr.toJson(); assertFalse(generatedJson.contains("client_ref")); + assertFalse(generatedJson.contains("webhook_url")); + assertFalse(generatedJson.contains("webhook_version")); assertTrue(generatedJson.contains("\"from\":\"447900000009\"")); assertTrue(generatedJson.contains("\"to\":\"12002009000\"")); assertTrue(generatedJson.contains("\"channel\":\"viber_service\"")); diff --git a/src/test/java/com/vonage/client/messages/MessageStatusTest.java b/src/test/java/com/vonage/client/messages/MessageStatusTest.java index d468d9cbe..96151538a 100644 --- a/src/test/java/com/vonage/client/messages/MessageStatusTest.java +++ b/src/test/java/com/vonage/client/messages/MessageStatusTest.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.vonage.client.VonageUnexpectedException; +import com.vonage.client.messages.whatsapp.ConversationType; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import java.net.URI; @@ -30,12 +31,12 @@ public class MessageStatusTest { @Test public void testSerdesAllFields() { UUID messageUuid = UUID.randomUUID(); - String to = "447700900000", from = "447700900001"; + String to = "447700900000", from = "447700900001", networkCode = "54321"; String timestamp = "2020-01-01T14:00:03.010Z"; MessageStatus.Status status = MessageStatus.Status.SUBMITTED; Channel channel = Channel.SMS; URI type = URI.create("https://developer.nexmo.com/api-errors/messages-olympus#1000"); - int title = 1000; + int title = 1000, countTotal = 3; String detail = "Throttled - You have exceeded the submission capacity allowed on this account."; String instance = "bf0ca0bf927b3b52e3cb03217e1a1ddf"; Currency currency = Currency.getInstance("EUR"); @@ -48,6 +49,19 @@ public void testSerdesAllFields() { MessageStatus.Usage usage = new MessageStatus.Usage(); usage.price = price; usage.currency = currency; + MessageStatus.Sms sms = new MessageStatus.Sms(); + sms.countTotal = countTotal; + MessageStatus.Destination destination = new MessageStatus.Destination(); + destination.networkCode = networkCode; + MessageStatus.Whatsapp whatsapp = new MessageStatus.Whatsapp(); + MessageStatus.Whatsapp.Conversation conversation = new MessageStatus.Whatsapp.Conversation(); + whatsapp.conversation = conversation; + MessageStatus.Whatsapp.Conversation.Origin origin = new MessageStatus.Whatsapp.Conversation.Origin(); + conversation.origin = origin; + ConversationType whatsappConversationType = ConversationType.REFERRAL_CONVERSION; + origin.type = whatsappConversationType; + String whatsappConversationId = "1234567890"; + conversation.id = whatsappConversationId; String json = "{\n" + " \"message_uuid\": \""+messageUuid+"\",\n" + @@ -65,7 +79,21 @@ public void testSerdesAllFields() { " \"usage\": {\n" + " \"currency\": \""+currency+"\",\n" + " \"price\": \""+price+"\"\n" + - " }\n" + + " },\n" + + " \"sms\": {\n" + + " \"count_total\": \""+countTotal+"\"\n" + + " },\n" + + " \"destination\": {\n" + + " \"network_code\": \""+networkCode+"\"\n" + + " },\n" + + " \"whatsapp\": {\n" + + " \"conversation\": {\n" + + " \"id\": \""+whatsappConversationId+"\",\n" + + " \"origin\": {\n" + + " \"type\": \""+whatsappConversationType+"\"\n" + + " }\n" + + " }\n" + + " }\n" + "}"; MessageStatus ms = MessageStatus.fromJson(json); @@ -88,6 +116,10 @@ public void testSerdesAllFields() { assertEquals(error.toString(), ms.getError().toString()); assertEquals(usage, ms.getUsage()); assertEquals(usage.toString(), ms.getUsage().toString()); + assertEquals(networkCode, ms.getDestinationNetworkCode()); + assertEquals(countTotal, ms.getSmsTotalCount()); + assertEquals(whatsappConversationId, ms.getWhatsappConversationId()); + assertEquals("referral_conversion", ms.getWhatsappConversationType().toString()); assertNull(ms.getAdditionalProperties()); } @@ -143,10 +175,10 @@ public void testDeserializeUnknownProperties() { " },\n" + " \"client_ref\": \"string\",\n" + " \"channel\": \"whatsapp\",\n" + - " \"whatsapp\": {\n" + - " \"conversation\": {\n" + - " \"id\": \"1234567890\",\n" + - " \"origin\": {\n" + + " \"wubwub\": {\n" + + " \"Prop0\": {\n" + + " \"id\": \"ab12cdhfgjk3\",\n" + + " \"N3s7ed\": {\n" + " \"type\": \"user_initiated\"\n" + " }\n" + " }\n" + @@ -156,10 +188,10 @@ public void testDeserializeUnknownProperties() { Map unknown = ms.getAdditionalProperties(); assertNotNull(unknown); assertEquals(1, unknown.size()); - Map whatsapp = (Map) unknown.get("whatsapp"); - Map conversation = (Map) whatsapp.get("conversation"); - assertEquals("1234567890", conversation.get("id")); - Map origin = (Map) conversation.get("origin"); + Map whatsapp = (Map) unknown.get("wubwub"); + Map conversation = (Map) whatsapp.get("Prop0"); + assertEquals("ab12cdhfgjk3", conversation.get("id")); + Map origin = (Map) conversation.get("N3s7ed"); assertEquals("user_initiated", origin.get("type")); } diff --git a/src/test/java/com/vonage/client/messages/mms/MmsVcardRequestTest.java b/src/test/java/com/vonage/client/messages/mms/MmsVcardRequestTest.java index cf79a213e..f57f1d55b 100644 --- a/src/test/java/com/vonage/client/messages/mms/MmsVcardRequestTest.java +++ b/src/test/java/com/vonage/client/messages/mms/MmsVcardRequestTest.java @@ -22,6 +22,20 @@ public class MmsVcardRequestTest { @Test public void testSerializeValid() { + String from = "447900000001", to = "317900000002", + url = "https://foo.tld/path/to/resource.vcf", caption = "!Alt text"; + + MmsVcardRequest mms = MmsVcardRequest.builder().url(url).from(from).to(to).caption(caption).build(); + String json = mms.toJson(); + assertTrue(json.contains("\"from\":\""+from+"\"")); + assertTrue(json.contains("\"to\":\""+to+"\"")); + assertTrue(json.contains("\"message_type\":\"vcard\"")); + assertTrue(json.contains("\"channel\":\"mms\"")); + assertTrue(json.contains("\"vcard\":{\"url\":\""+url+"\",\"caption\":\""+caption+"\"}")); + } + + @Test + public void testSerializeNoCaption() { String from = "447900000001", to = "317900000002", url = "https://foo.tld/path/to/resource.vcf"; @@ -37,7 +51,7 @@ public void testSerializeValid() { @Test public void testConstructNoUrl() { assertThrows(NullPointerException.class, () -> - MmsVcardRequest.builder().from("447900000001").to("317900000002").build() + MmsVcardRequest.builder().caption("Cap").from("447900000001").to("317900000002").build() ); } diff --git a/src/test/java/com/vonage/client/messages/sms/SmsTextRequestTest.java b/src/test/java/com/vonage/client/messages/sms/SmsTextRequestTest.java index 2cc479cbe..aaca698db 100644 --- a/src/test/java/com/vonage/client/messages/sms/SmsTextRequestTest.java +++ b/src/test/java/com/vonage/client/messages/sms/SmsTextRequestTest.java @@ -19,32 +19,103 @@ import static org.junit.jupiter.api.Assertions.*; public class SmsTextRequestTest { + final String + from = "447900000001", to = "317900000002", msg = "Hello, World!", + contentId = "1107457532145798767", entityId = "1101456324675322134"; @Test - public void testSerializeValid() { - String from = "447900000001", to = "317900000002", msg = "Hello, World!"; + public void testSerializeAllParameters() { + int ttl = 900000; + SmsTextRequest sms = SmsTextRequest.builder() + .from(from).to(to).text(msg).ttl(ttl).contentId(contentId) + .encodingType(EncodingType.UNICODE).entityId(entityId).build(); + + String json = sms.toJson(); + assertTrue(json.contains("\"text\":\""+msg+"\"")); + assertTrue(json.contains("\"from\":\""+from+"\"")); + assertTrue(json.contains("\"to\":\""+to+"\"")); + assertTrue(json.contains("\"message_type\":\"text\"")); + assertTrue(json.contains("\"channel\":\"sms\"")); + assertTrue(json.contains("\"ttl\":"+ttl)); + assertTrue(json.contains("\"sms\":{" + + "\"encoding_type\":\"unicode\"," + + "\"content_id\":\""+contentId+"\"," + + "\"entity_id\":\""+entityId+"\"}" + )); + assertEquals("SmsTextRequest "+json, sms.toString()); + } + + @Test + public void testRequiredParametersOnly() { SmsTextRequest sms = SmsTextRequest.builder().from(from).to(to).text(msg).build(); + String json = sms.toJson(); assertTrue(json.contains("\"text\":\""+msg+"\"")); assertTrue(json.contains("\"from\":\""+from+"\"")); assertTrue(json.contains("\"to\":\""+to+"\"")); assertTrue(json.contains("\"message_type\":\"text\"")); assertTrue(json.contains("\"channel\":\"sms\"")); + assertEquals("SmsTextRequest "+json, sms.toString()); + + assertNull(sms.getMessageSettings()); + assertNull(sms.getTtl()); + } + @Test + public void testEncodingTypeSettingsOnly() { + EncodingType encodingType = EncodingType.AUTO; + SmsTextRequest sms = SmsTextRequest.builder() + .from(from).to(to).text(msg).encodingType(encodingType).build(); + + String json = sms.toJson(); + assertTrue(json.contains("\"text\":\""+msg+"\"")); + assertTrue(json.contains("\"from\":\""+from+"\"")); + assertTrue(json.contains("\"to\":\""+to+"\"")); + assertTrue(json.contains("\"message_type\":\"text\"")); + assertTrue(json.contains("\"channel\":\"sms\"")); + assertTrue(json.contains("\"sms\":{\"encoding_type\":\""+encodingType+"\"}")); assertEquals("SmsTextRequest "+json, sms.toString()); + + assertNull(sms.getTtl()); + OutboundSettings settings = sms.getMessageSettings(); + assertNotNull(settings); + assertEquals(encodingType, EncodingType.fromString(settings.getEncodingType().toString())); + assertNull(settings.getEntityId()); + assertNull(settings.getContentId()); + } + + @Test + public void testInvalidContentId() { + assertThrows(IllegalArgumentException.class, () -> + OutboundSettings.construct(EncodingType.TEXT, " ", entityId) + ); + } + + @Test + public void testInvalidEntityId() { + assertThrows(IllegalArgumentException.class, () -> + OutboundSettings.construct(EncodingType.TEXT, contentId, " ") + ); + } + + @Test + public void testTtlTooShort() { + assertThrows(IllegalArgumentException.class, () -> + SmsTextRequest.builder().from(from).to(to).text(msg).ttl(0).build() + ); } @Test public void testNullText() { - assertThrows(NullPointerException.class, () -> SmsTextRequest.builder() - .from("447900000001").to("317900000002").build() + assertThrows(NullPointerException.class, () -> + SmsTextRequest.builder().from(from).to(to).build() ); } @Test public void testEmptyText() { - assertThrows(IllegalArgumentException.class, () -> SmsTextRequest.builder() - .from("447900000001").to("317900000002").text("").build() + assertThrows(IllegalArgumentException.class, () -> + SmsTextRequest.builder().from(from).to(to).text("").build() ); } @@ -56,11 +127,7 @@ public void testLongText() { } assertEquals(999, text.length()); - SmsTextRequest sms = SmsTextRequest.builder() - .text(text.toString()) - .from("447900000001") - .to("317900000002") - .build(); + SmsTextRequest sms = SmsTextRequest.builder().text(text.toString()).from(from).to(to).build(); assertEquals(text.toString(), sms.getText()); text.append("xy"); diff --git a/src/test/java/com/vonage/client/messages/whatsapp/WhatsappFileRequestTest.java b/src/test/java/com/vonage/client/messages/whatsapp/WhatsappFileRequestTest.java index efd584e9a..5c5d7a0e1 100644 --- a/src/test/java/com/vonage/client/messages/whatsapp/WhatsappFileRequestTest.java +++ b/src/test/java/com/vonage/client/messages/whatsapp/WhatsappFileRequestTest.java @@ -22,22 +22,25 @@ public class WhatsappFileRequestTest { @Test public void testSerializeValid() { - String url = "file:///path/to/attachment.zip", caption = "Srs bzns"; + String url = "file:///path/to/attachment.zip", caption = "Srs bzns", name = "Stuff"; String json = WhatsappFileRequest.builder() .from("317900000002").to("447900000001") - .url(url).caption(caption) + .url(url).caption(caption).name(name) .build().toJson(); - assertTrue(json.contains("\"file\":{\"url\":\""+url+"\",\"caption\":\""+caption+"\"}")); + assertTrue(json.contains( + "\"file\":{\"url\":\""+url+ "\",\"caption\":\""+caption+"\",\"name\":\""+name+"\"}" + )); assertTrue(json.contains("\"message_type\":\"file\"")); assertTrue(json.contains("\"channel\":\"whatsapp\"")); } @Test - public void testSerializeNoCaption() { + public void testSerializeNoCaptionOrName() { String url = "file:///path/to/spec.pdf"; - String json = WhatsappFileRequest.builder() - .url(url).from("447900000002").to("447900000001") - .build().toJson(); + WhatsappFileRequest req = WhatsappFileRequest.builder() + .url(url).from("447900000002").to("447900000001").build(); + assertNull(req.getFile().getName()); + String json = req.toJson(); assertTrue(json.contains("\"file\":{\"url\":\""+url+"\"}")); assertTrue(json.contains("\"message_type\":\"file\"")); assertTrue(json.contains("\"channel\":\"whatsapp\""));