From 6e799a0a1fe372c08edeac838c7013436d95e704 Mon Sep 17 00:00:00 2001 From: Karl Lingiah Date: Wed, 28 Aug 2024 10:54:07 +0100 Subject: [PATCH] DEVX-8697: Messages API Updates for RCS and WhatsApp (#316) * Implementing RCS channel in Messages API * Implementing Messaging update method * Adding code somments for update method * Adding rcs to CHANNELS hash in Message class * Bumping version and updating changelog --- CHANGES.md | 7 + lib/vonage.rb | 1 + lib/vonage/messaging.rb | 16 +- lib/vonage/messaging/channels/rcs.rb | 42 ++++ lib/vonage/messaging/message.rb | 1 + lib/vonage/version.rb | 2 +- test/vonage/messaging/channels/rcs_test.rb | 226 ++++++++++++++++++ .../messaging/channels/whats_app_test.rb | 2 +- test/vonage/messaging_test.rb | 14 ++ test/vonage/test.rb | 4 + 10 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 lib/vonage/messaging/channels/rcs.rb create mode 100644 test/vonage/messaging/channels/rcs_test.rb diff --git a/CHANGES.md b/CHANGES.md index 179f8266..77001cdb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +# 7.27.0 + +* Updates Messages API implementation to add RCS channel as well as a new `PATCH` endpoint for RCS message revocation and WhatsApp Mark as Read features. [#316](https://github.com/Vonage/vonage-ruby-sdk/pull/316) +* Updates to `talk`, `stream`, `input`, and `record` NCCOs in Voice API implementation. [#315](https://github.com/Vonage/vonage-ruby-sdk/pull/315) +* Adds deprecation warnings to Meetings API and Proactive Connect API implementations, and updates code comments for Numbers API. [#314](https://github.com/Vonage/vonage-ruby-sdk/pull/314) + + # 7.26.0 * Implements the Network Number Verification and Network SIM Swap APIs. [#313](https://github.com/Vonage/vonage-ruby-sdk/pull/313) diff --git a/lib/vonage.rb b/lib/vonage.rb index 53e28e98..9c77ef63 100644 --- a/lib/vonage.rb +++ b/lib/vonage.rb @@ -13,6 +13,7 @@ module Vonage 'http' => 'HTTP', 'json' => 'JSON', 'jwt' => 'JWT', + 'rcs' => 'RCS', 'sip' => 'SIP', 'sms' => 'SMS', 'network_sim_swap' => 'NetworkSIMSwap', diff --git a/lib/vonage/messaging.rb b/lib/vonage/messaging.rb index f06d76b8..904d177c 100644 --- a/lib/vonage/messaging.rb +++ b/lib/vonage/messaging.rb @@ -25,12 +25,26 @@ class Messaging < Namespace # @option params [required, Hash] **message # The Vonage Message object to use for this message. # - # @see https://developer.vonage.com/api/messages-olympus#SendMessage + # @see https://developer.vonage.com/api/messages#SendMessage # def send(to:, from:, **message) request('/v1/messages', params: {to: to, from: from, **message}, type: Post) end + # Update a Message Object. + # + # @example + # message = client.messaging.update(message_uuid: "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab", status: "read") + # + # @option params [required, String] :message_uuid. the UUID of the message to update. + # + # `:message_uuid` is always required. Other parameters will depend on the message channel and the specific action being performed. + # @see https://developer.vonage.com/api/messages#UpdateMessage + # + def update(message_uuid:, **params) + request("/v1/messages/#{message_uuid}", params: params, type: Patch) + end + # Validate a JSON Web Token from a Messages API Webhook. # # @param [String, required] :token The JWT from the Webhook's Authorization header diff --git a/lib/vonage/messaging/channels/rcs.rb b/lib/vonage/messaging/channels/rcs.rb new file mode 100644 index 00000000..e473796d --- /dev/null +++ b/lib/vonage/messaging/channels/rcs.rb @@ -0,0 +1,42 @@ +# typed: true + +module Vonage + class Messaging::Channels::RCS < Messaging::Message + MESSAGE_TYPES = ['text', 'image', 'video', 'file', 'custom'] + + attr_reader :data + + def initialize(attributes = {}) + @type = attributes.fetch(:type, nil) + @message = attributes.fetch(:message, nil) + @opts = attributes.fetch(:opts, {}) + @data = {} + + after_initialize! + end + + private + + def build + data[:channel] = 'rcs' + super + end + + def verify_type + raise ClientError.new("Invalid message type") unless MESSAGE_TYPES.include?(type) + end + + def verify_message + case type + when 'text' + raise Vonage::ClientError.new("Invalid parameter type. `:message` must be a String") unless message.is_a? String + when 'custom' + raise Vonage::ClientError.new("Invalid parameter type. `:message` must be a Hash") unless message.is_a? Hash + raise Vonage::ClientError.new("Invalid parameter content. `:message` must not be empty") if message.empty? + else + raise Vonage::ClientError.new("Invalid parameter type. `:message` must be a Hash") unless message.is_a? Hash + raise Vonage::ClientError.new("Missing parameter. `:message` must contain a `:url` key") unless message[:url] + end + end + end +end diff --git a/lib/vonage/messaging/message.rb b/lib/vonage/messaging/message.rb index 8d2a7bac..0fcd1537 100644 --- a/lib/vonage/messaging/message.rb +++ b/lib/vonage/messaging/message.rb @@ -5,6 +5,7 @@ class Messaging::Message CHANNELS = { sms: Vonage::Messaging::Channels::SMS, mms: Vonage::Messaging::Channels::MMS, + rcs: Vonage::Messaging::Channels::RCS, whatsapp: Vonage::Messaging::Channels::WhatsApp, messenger: Vonage::Messaging::Channels::Messenger, viber: Vonage::Messaging::Channels::Viber diff --git a/lib/vonage/version.rb b/lib/vonage/version.rb index a7a02697..c65edb93 100644 --- a/lib/vonage/version.rb +++ b/lib/vonage/version.rb @@ -1,5 +1,5 @@ # typed: strong module Vonage - VERSION = '7.26.0' + VERSION = '7.27.0' end diff --git a/test/vonage/messaging/channels/rcs_test.rb b/test/vonage/messaging/channels/rcs_test.rb new file mode 100644 index 00000000..fb0e8d08 --- /dev/null +++ b/test/vonage/messaging/channels/rcs_test.rb @@ -0,0 +1,226 @@ +# typed: false + + +class Vonage::Messaging::Channels::RCSTest < Vonage::Test + def test_rcs_initialize + message = Vonage::Messaging::Channels::RCS.new(type: 'text', message: 'Hello world!') + + assert_kind_of Vonage::Messaging::Channels::RCS, message + end + + def test_rcs_text_message + expected = { + channel: 'rcs', + message_type: 'text', + text: 'Hello world!' + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'text', + message: 'Hello world!' + ) + + assert_equal expected, message.data + end + + def test_rcs_text_message_wth_optional_parameters + expected = { + channel: 'rcs', + message_type: 'text', + text: 'Hello world!', + ttl: 600, + client_ref: "abc123", + webhook_url: "https://example.com/status" + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'text', + message: 'Hello world!', + opts: { + ttl: 600, + client_ref: "abc123", + webhook_url: "https://example.com/status" + } + ) + + assert_equal expected, message.data + end + + def test_rcs_image_message + expected = { + channel: 'rcs', + message_type: 'image', + image: { + url: 'https://example.com/image.jpg' + } + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'image', + message: { + url: 'https://example.com/image.jpg' + } + ) + + assert_equal expected, message.data + end + + def test_rcs_video_message + expected = { + channel: 'rcs', + message_type: 'image', + image: { + url: 'https://example.com/video.webm' + } + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'image', + message: { + url: 'https://example.com/video.webm' + } + ) + + assert_equal expected, message.data + end + + def test_rcs_file_message + expected = { + channel: 'rcs', + message_type: 'file', + file: { + url: 'https://example.com/file.pdf' + } + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'file', + message: { + url: 'https://example.com/file.pdf' + } + ) + + assert_equal expected, message.data + end + + def test_rcs_custom_message + expected = { + channel: 'rcs', + message_type: 'custom', + custom: { + contentMessage: { + text: 'Which ice-cream flavour do you prefer?', + suggestions: [ + { + reply: { + text: 'Vanilla', + postback: 'vanilla' + } + }, + { + reply: { + text: 'Chocolate', + postback: 'chocolate' + } + } + ] + } + } + } + + message = Vonage::Messaging::Channels::RCS.new( + type: 'custom', + message: { + contentMessage: { + text: 'Which ice-cream flavour do you prefer?', + suggestions: [ + { + reply: { + text: 'Vanilla', + postback: 'vanilla' + } + }, + { + reply: { + text: 'Chocolate', + postback: 'chocolate' + } + } + ] + } + } + ) + + assert_equal expected, message.data + end + + def test_rcs_invalid_message_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'invalid', message: 'Hello world!') } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid message type", exception.message + end + + def test_rcs_text_message_invalid_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'text', message: 123) } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter type. `:message` must be a String", exception.message + end + + def test_rcs_image_message_invalid_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'image', message: 'https://example.com/image.jpg') } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter type. `:message` must be a Hash", exception.message + end + + def test_rcs_image_message_missing_url + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'image', message: {}) } + + assert_instance_of Vonage::ClientError, exception + assert_match "Missing parameter. `:message` must contain a `:url` key", exception.message + end + + def test_rcs_video_message_invalid_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'video', message: 'https://example.com/video.webm') } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter type. `:message` must be a Hash", exception.message + end + + def test_rcs_video_message_missing_url + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'video', message: {}) } + + assert_instance_of Vonage::ClientError, exception + assert_match "Missing parameter. `:message` must contain a `:url` key", exception.message + end + + def test_rcs_file_message_invalid_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'file', message: 'https://example.com/file.pdf') } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter type. `:message` must be a Hash", exception.message + end + + def test_rcs_file_message_missing_url + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'file', message: {}) } + + assert_instance_of Vonage::ClientError, exception + assert_match "Missing parameter. `:message` must contain a `:url` key", exception.message + end + + def test_rcs_custom_message_invalid_type + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'custom', message: 'Hello world!') } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter type. `:message` must be a Hash", exception.message + end + + def test_rcs_custom_message_with_empty_message_hash + exception = assert_raises { Vonage::Messaging::Channels::RCS.new(type: 'custom', message: {}) } + + assert_instance_of Vonage::ClientError, exception + assert_match "Invalid parameter content. `:message` must not be empty", exception.message + end +end diff --git a/test/vonage/messaging/channels/whats_app_test.rb b/test/vonage/messaging/channels/whats_app_test.rb index 3c90629d..9d45bd11 100644 --- a/test/vonage/messaging/channels/whats_app_test.rb +++ b/test/vonage/messaging/channels/whats_app_test.rb @@ -2,7 +2,7 @@ class Vonage::Messaging::Channels::WhatsAppTest < Vonage::Test - def test_messenger_initialize + def test_whats_app_initialize whatsapp = Vonage::Messaging::Channels::WhatsApp.new(type: 'text', message: 'Hello world!') assert_kind_of Vonage::Messaging::Channels::WhatsApp, whatsapp diff --git a/test/vonage/messaging_test.rb b/test/vonage/messaging_test.rb index 66f70d4d..06ba0069 100644 --- a/test/vonage/messaging_test.rb +++ b/test/vonage/messaging_test.rb @@ -6,6 +6,14 @@ def messaging Vonage::Messaging.new(config) end + def geo_specific_messaging_host + 'api-eu.vonage.com' + end + + def geo_specific_messaging + Vonage::Messaging.new(config.merge(api_host: geo_specific_messaging_host)) + end + def messaging_uri 'https://api.nexmo.com/v1/messages' end @@ -65,4 +73,10 @@ def test_verify_webhook_token_method_with_invalid_secret def test_verify_webhook_token_method_with_no_token assert_raises(ArgumentError) { messaging.verify_webhook_token } end + + def test_update_method + stub_request(:patch, 'https://' + geo_specific_messaging_host + '/v1/messages/' + message_uuid).with(request(body: {status: 'read'})).to_return(response) + + assert_kind_of Vonage::Response, geo_specific_messaging.update(message_uuid: message_uuid, status: 'read') + end end diff --git a/test/vonage/test.rb b/test/vonage/test.rb index 1b51b1eb..84e49385 100644 --- a/test/vonage/test.rb +++ b/test/vonage/test.rb @@ -406,6 +406,10 @@ def meetings_id "MEET-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" end + def message_uuid + "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab" + end + def video_id 'VIDEO-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' end