diff --git a/CHANGELOG.md b/CHANGELOG.md index cecf5df..ab1d6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- Implement a Better response API [159](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/156) - Implement a Better API for calling the Cloud API [156](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/156) - Used [VCR gem]() for tests @igacio-chiazzo [153](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/153) - Added support for API v20.0 and v21.0 @guizaols [152](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/152) diff --git a/README.md b/README.md index ac44a58..21a7b84 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,27 @@ Check the [example.rb file](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk +## Sneak Peek + +```ruby +client = WhatsappSdk::Api::Client.new("") # replace this with a valid access token + +client.phone_numbers.register_number(SENDER_ID, 123456) # register the phone number to uplaod media and send message from. + +# send a text and a location +client.messages.send_text(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER,message: "Hey there! it's Whatsapp Ruby SDK") + +client.messages.send_location(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, longitude: -75.6898604, latitude: 45.4192206, name: "Ignacio", address: "My house") + +# upload a photo and send it +image = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/whatsapp.png", type: "image/png")image = client.media.get(media_id: uploaded_media.id) +client.messages.send_image(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, image_id: image.id) + +# upload a sticker and send it +sticker = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/sticker.webp", type: "image/webp") +client.messages.send_sticker(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, sticker_id: sticker.id) +``` + ## APIs ### Templates @@ -290,12 +311,12 @@ client.messages.send_contacts(sender_id: 123123, recipient_number: 56789, contac ```ruby currency = WhatsappSdk::Resource::Currency.new(code: "USD", amount: 1000, fallback_value: "1000") date_time = WhatsappSdk::Resource::DateTime.new(fallback_value: "2020-01-01T00:00:00Z") -image = WhatsappSdk::Resource::Media.new(type: "image", link: "http(s)://URL") +media_component = WhatsappSdk::Resource::MediaComponent.new(type: "image", link: "http(s)://URL") location = WhatsappSdk::Resource::Location.new( latitude: 25.779510, longitude: -80.338631, name: "miami store", address: "820 nw 87th ave, miami, fl" ) -parameter_image = WhatsappSdk::Resource::ParameterObject.new(type: "image", image: image) +parameter_image = WhatsappSdk::Resource::ParameterObject.new(type: "image", image: media_component) parameter_text = WhatsappSdk::Resource::ParameterObject.new(type: "text", text: "TEXT_STRING") parameter_currency = WhatsappSdk::Resource::ParameterObject.new(type: "currency", currency: currency) parameter_date_time = WhatsappSdk::Resource::ParameterObject.new(type: "date_time", date_time: date_time) @@ -327,7 +348,9 @@ button_component2 = WhatsappSdk::Resource::Component.new( ) location_component = WhatsappSdk::Resource::Component.new(type: "header", parameters: [parameter_location]) -client.messages.send_template(sender_id: 12_345, recipient_number: 12345678, name: "hello_world", language: "en_US", components_json: [component_1]) +client.messages.send_template( + sender_id: 12_345, recipient_number: 12345678, name: "hello_world", language: "en_US", components: [...] +) ``` @@ -370,7 +393,7 @@ interactive_list_messages = WhatsappSdk::Resource::Interactive.new( ) client.messages.send_interactive_list_messages( - sender_id: 12_345, recipient_number: 1234567890, + sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, interactive: interactive_list_messages ) ``` @@ -434,6 +457,10 @@ client.messages.send_interactive_reply_buttons( +### Errors + +If the API returns an error then an exception `WhatsappSdk::Api::Responses::HttpResponseError` is raised. The object contains information returned by the Cloud API. For more information about the potential error check the [official documentation](https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/). + ## Examples Visit [the example file](/example.rb) with examples to call the API in a single file. diff --git a/example.rb b/example.rb index e2568ef..8c2f390 100644 --- a/example.rb +++ b/example.rb @@ -42,35 +42,33 @@ ################# HELPERS ######################## def print_message_sent(message_response, type = "") - if message_response.ok? - puts "Message #{type} sent to: #{message_response.data.contacts.first.input}" - else - puts "Error: #{message_response.error.message}" - end + puts "Message #{type} sent to: #{message_response.contacts.first.input}" end -def print_data_or_error(response, identifier) - if response.error? - return "Error: #{response.error&.to_s}" +def run_and_catch_error(message, &block) + begin + yield + rescue WhatsappSdk::Api::Responses::HttpResponseError => e + puts "Error: #{e}" end - - return identifier end ################################################## + client = WhatsappSdk::Api::Client.new -############################## Templates API ############################## + +# ############################## Templates API ############################## puts "\n\n ------------------ Testing Templates API ------------------------" -## Get list of templates +# ## Get list of templates templates = client.templates.list(business_id: BUSINESS_ID) -puts "GET Templates list : #{print_data_or_error(templates, templates.data&.templates.map { |r| r.template.name })}" +puts "GET Templates list : #{ templates.records.map(&:name) }" ## Get message templates namespace template_namespace = client.templates.get_message_template_namespace(business_id: BUSINESS_ID) -puts "GET template by namespace: #{print_data_or_error(template_namespace, template_namespace.data&.id)}" +puts "GET template by namespace: #{template_namespace.id}" # Create a template components_json = [ @@ -102,11 +100,15 @@ def print_data_or_error(response, identifier) } ] -new_template = client.templates.create( - business_id: BUSINESS_ID, name: "seasonal_promotion", language: "ka", category: "MARKETING", - components_json: components_json, allow_category_change: true -) -puts "GET template by namespace: #{print_data_or_error(template_namespace, template_namespace.data&.id)}" +new_template = nil + +run_and_catch_error("Create a template") do + new_template = client.templates.create( + business_id: BUSINESS_ID, name: "seasonal_promotion_2", language: "ka", category: "MARKETING", + components_json: components_json, allow_category_change: true + ) + puts "GET template by namespace: #{template_namespace.id}" +end # Update a template components_json = [ @@ -123,57 +125,91 @@ def print_data_or_error(response, identifier) } ] -if new_template&.data&.id - updated_template = client.templates.update(template_id: new_template&.data.id, category: "UTILITY") - puts "UPDATE template by id: #{print_data_or_error(updated_template, updated_template.data&.id)}" +if new_template + updated_template = client.templates.update(template_id: new_template.id, category: "UTILITY") + puts "UPDATE template by id: #{updated_template.id}" end ## Delete a template -delete_template = client.templates.delete(business_id: BUSINESS_ID, name: "seasonal_promotion") # delete by name -puts "Delete template by id: #{print_data_or_error(delete_template, delete_template.data&.id) }" +run_and_catch_error("Delete a template") do + delete_template = client.templates.delete(business_id: BUSINESS_ID, name: "seasonal_promotion") # delete by name + puts "Delete template by id: #{delete_template.id}" +end # client.templates.delete(business_id: BUSINESS_ID, name: "name2", hsm_id: "243213188351928") # delete by name and id -############################## Business API ############################## +# ############################## Business API ############################## puts "\n\n\n ------------------ Testing Business API -----------------------" business_profile = client.business_profiles.get(SENDER_ID) -puts "DELETE Business Profile by id: #{print_data_or_error(delete_template, business_profile.data&.about) }" +puts "GET Business Profile by id: #{business_profile.about}" + +updated_bp = client.business_profiles.update(phone_number_id: SENDER_ID, params: { websites: ["www.ignaciochiazzo.com"] } ) +puts "UPDATE Business Profile by id: #{updated_bp} }" + +run_and_catch_error("Update business profile") do + # about can't be set + updated_bp = client.business_profiles.update(phone_number_id: SENDER_ID, params: { about: "A cool business" } ) +end -updated_bp = client.business_profiles.update(phone_number_id: SENDER_ID, params: { about: "A very cool business" } ) -puts "UPDATE Business Profile by id: #{print_data_or_error(updated_bp, updated_bp.data&.success?) }" ############################## Phone Numbers API ############################## puts "\n\n\n ------------------ Testing Phone Numbers API -----------------------" + +# Get phone numbers registered_number = client.phone_numbers.get(SENDER_ID) -puts "GET Registered number: #{print_data_or_error(registered_number, registered_number.data&.id)}" +puts "GET Registered number: #{registered_number.id}" +# Get phone number registered_numbers = client.phone_numbers.list(BUSINESS_ID) -puts "GET Registered numbers: #{print_data_or_error(registered_number, registered_numbers.data&.phone_numbers.map(&:id))}" +puts "GET Registered numbers: #{registered_numbers.records.map(&:id)}" + +# Deregister a phone number - I skip registering so that the number can upload media +# run_and_catch_error("Deregister a phone number") do +# deregister_number_result = client.phone_numbers.deregister_number(SENDER_ID) +# puts "DEREGISTER number: #{deregister_number_result}" +# end + +# Register a phone number +run_and_catch_error("Register a phone number") do + register_number_result = client.phone_numbers.register_number(SENDER_ID, 123456) + puts "REGISTER number: #{register_number_result}" +end + +# Register a fake number +begin + fake_number = "1234567890" + client.phone_numbers.register_number(fake_number, 123456) +rescue WhatsappSdk::Api::Responses::HttpResponseError => e + puts "Error: #{e}" +end + ############################## Media API ############################## puts "\n\n\n ------------------ Testing Media API" ##### Image ##### # upload a Image -uploaded_media = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/whatsapp.png", type: "image/png") -puts "Uploaded image id: #{print_data_or_error(uploaded_media, uploaded_media.data&.id)}" +run_and_catch_error("Upload a Image") do + uploaded_media = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/whatsapp.png", type: "image/png") + puts "Uploaded image id: #{uploaded_media&.id}" +end # get a media Image -if uploaded_media.data&.id - image = client.media.get(media_id: uploaded_media.data&.id) - puts "GET image id: #{print_data_or_error(image, image.data&.id)}" +if uploaded_media&.id + image = client.media.get(media_id: uploaded_media.id) + puts "GET image id: #{image.id}" # download media Image - download_image = client.media.download(url: image.data.url, file_path: 'test/fixtures/assets/downloaded_image.png', media_type: "image/png") - puts "Downloaded Image: #{print_data_or_error(download_image, download_image.data.success?)}" + download_image = client.media.download(url: image.url, file_path: 'test/fixtures/assets/downloaded_image.png', media_type: "image/png") + puts "Downloaded Image: #{download_image}" uploaded_media = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/whatsapp.png", type: "image/png") # delete a media - deleted_media = client.media.delete(media_id: uploaded_media.data.id) - puts "Delete image: #{print_data_or_error(deleted_media, deleted_media.data.success?)}" + deleted_media = client.media.delete(media_id: uploaded_media.id) + puts "Delete image: #{deleted_media.success?}" else puts "No media to download and delete" end @@ -181,44 +217,44 @@ def print_data_or_error(response, identifier) #### Video #### # upload a video uploaded_video = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/riquelme.mp4", type: "video/mp4") -puts "Uploaded video: #{print_data_or_error(uploaded_media, uploaded_video.data&.id)}" +puts "Uploaded video: #{uploaded_video.id}" -video = client.media.get(media_id: uploaded_video.data&.id) +video = client.media.get(media_id: uploaded_video.id) # upload a video uploaded_video = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/riquelme.mp4", type: "video/mp4") -puts "Uploaded video id: #{print_data_or_error(uploaded_media, video.data&.id)}" +puts "Uploaded video id: #{uploaded_video.id}" #### Audio #### # upload an audio audio_response = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/downloaded_audio.ogg", type: "audio/ogg") -puts "Uploaded audio id: #{print_data_or_error(audio_response, audio_response.data&.id)}" +puts "Uploaded audio id: #{audio_response.id}" -if audio_response.data&.id - audio_id = audio_response.data&.id +if audio_response&.id + audio_id = audio_response&.id # get a media audio audio = client.media.get(media_id: audio_id) - puts "GET Audio id: #{print_data_or_error(audio, audio_id)}" + puts "GET Audio id: #{audio.id}" # get a media audio - audio_link = audio.data.url + audio_link = audio.url download_image = client.media.download(url: audio_link, file_path: 'test/fixtures/assets/downloaded_audio2.ogg', media_type: "audio/ogg") - puts "Download Audio: #{print_data_or_error(download_image, download_image.data.success?)}" + puts "Download Audio: #{download_image}" end # upload a document document_response = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/document.pdf", type: "application/pdf") -puts "Uploaded document id: #{print_data_or_error(document_response, document_response.data&.id)}" +puts "Uploaded document id: #{document_response.id}" -document = client.media.get(media_id: document_response.data&.id) -puts "GET document id: #{print_data_or_error(document, document.data&.id)}" +document = client.media.get(media_id: document_response.id) +puts "GET document id: #{document.id}" # upload a sticker sticker_response = client.media.upload(sender_id: SENDER_ID, file_path: "test/fixtures/assets/sticker.webp", type: "image/webp") -puts "Uploaded sticker id: #{print_data_or_error(sticker_response, sticker_response.data&.id)}" +puts "Uploaded sticker id: #{sticker_response.id}" -sticker = client.media.get(media_id: sticker_response.data&.id) -puts "GET Sticker id: #{print_data_or_error(sticker, sticker.data&.id)}" +sticker = client.media.get(media_id: sticker_response.id) +puts "GET Sticker id: #{sticker.id}" @@ -231,7 +267,7 @@ def print_data_or_error(response, identifier) print_message_sent(message_sent, "text") ######### React to a message -message_id = message_sent.data&.messages.first.id +message_id = message_sent&.messages.first.id reaction_1_sent = client.messages.send_reaction( sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, @@ -241,11 +277,11 @@ def print_data_or_error(response, identifier) reaction_2_sent = client.messages.send_reaction(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, message_id: message_id, emoji: "⛄️") if message_id -puts "Message Reaction 1: #{print_data_or_error(reaction_1_sent, reaction_1_sent.data&.messages.first&.id)}" -puts "Message Reaction 2: #{print_data_or_error(reaction_2_sent, reaction_2_sent.data&.messages.first&.id)}" +puts "Message Reaction 1: #{reaction_1_sent&.messages.first&.id}" +puts "Message Reaction 2: #{reaction_2_sent&.messages.first&.id}" ######### Reply to a message -message_to_reply_id = message_sent.data.messages.first.id +message_to_reply_id = message_sent.messages.first.id reply = client.messages.send_text(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, message: "I'm a reply", message_id: message_to_reply_id) print_message_sent(reply, "reply") @@ -263,51 +299,51 @@ def print_data_or_error(response, identifier) ######### SEND AN IMAGE # Send an image with a link -if image.data&.id +if image&.id image_sent = client.messages.send_image( - sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: image.data.url, caption: "Ignacio Chiazzo Profile" + sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: image.url, caption: "Ignacio Chiazzo Profile" ) print_message_sent(image_sent, "image via url") # Send an image with an id client.messages.send_image( - sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, image_id: image.data.id, caption: "Ignacio Chiazzo Profile" + sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, image_id: image.id, caption: "Ignacio Chiazzo Profile" ) print_message_sent(image_sent, "image via id") end ######### SEND AUDIOS ## with a link -audio_sent = client.messages.send_audio(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: audio.data.url) +audio_sent = client.messages.send_audio(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: audio.url) print_message_sent(audio_sent, "audio via url") ## with an audio id -audio_sent = client.messages.send_audio(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, audio_id: audio.data.id) +audio_sent = client.messages.send_audio(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, audio_id: audio.id) print_message_sent(audio_sent, "audio via id") ######### SEND DOCUMENTS ## with a link -if document.data&.id +if document&.id document_sent = client.messages.send_document( - sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: document.data&.url, caption: "Ignacio Chiazzo" + sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: document&.url, caption: "Ignacio Chiazzo" ) print_message_sent(document_sent, "document via url") ## with a document id document_sent = client.messages.send_document( - sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, document_id: document.data&.id, caption: "Ignacio Chiazzo" + sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, document_id: document&.id, caption: "Ignacio Chiazzo" ) # modify print_message_sent(document_sent, "document via id") end ######### SEND STICKERS -if sticker.data&.id +if sticker&.id ## with a link - sticker_sent = client.messages.send_sticker(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: sticker.data.url) + sticker_sent = client.messages.send_sticker(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, link: sticker.url) print_message_sent(sticker_sent, "sticker via url") ## with a sticker_id - sticker_sent = client.messages.send_sticker(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, sticker_id: sticker.data.id) + sticker_sent = client.messages.send_sticker(sender_id: SENDER_ID, recipient_number: RECIPIENT_NUMBER, sticker_id: sticker.id) print_message_sent(sticker_sent, "sticker via id") end @@ -323,9 +359,9 @@ def print_data_or_error(response, identifier) header_component = WhatsappSdk::Resource::Component.new( type: WhatsappSdk::Resource::Component::Type::HEADER ) -image = WhatsappSdk::Resource::Media.new(type: "image", link: "http(s)://URL", caption: "caption") -document = WhatsappSdk::Resource::Media.new(type: "document", link: "http(s)://URL", filename: "txt.rb") -video = WhatsappSdk::Resource::Media.new(type: "video", id: "123") +image = WhatsappSdk::Resource::MediaComponent.new(type: "image", link: "http(s)://URL", caption: "caption") +document = WhatsappSdk::Resource::MediaComponent.new(type: "document", link: "http(s)://URL", filename: "txt.rb") +video = WhatsappSdk::Resource::MediaComponent.new(type: "video", id: "123") location = WhatsappSdk::Resource::Location.new( latitude: 25.779510, longitude: -80.338631, name: "miami store", address: "820 nw 87th ave, miami, fl" ) diff --git a/lib/whatsapp_sdk/api/business_profile.rb b/lib/whatsapp_sdk/api/business_profile.rb index 052cabd..59effcc 100644 --- a/lib/whatsapp_sdk/api/business_profile.rb +++ b/lib/whatsapp_sdk/api/business_profile.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "request" -require_relative "response" require_relative "../resource/business_profile" module WhatsappSdk @@ -22,7 +21,7 @@ def initialize(vertical:) # Get the details of business profile. # # @param phone_number_id [Integer] Phone Number Id. - # @return [Api::Response] Response object. + # @return [Resource::BusinessProfile] Response object. def get(phone_number_id, fields: nil) fields = if fields fields.join(',') @@ -35,10 +34,8 @@ def get(phone_number_id, fields: nil) endpoint: "#{phone_number_id}/whatsapp_business_profile?fields=#{fields}" ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::BusinessProfileDataResponse - ) + # In the future it might have multiple business profiles. + Resource::BusinessProfile.from_hash(response["data"][0]) end def details(phone_number_id, fields: nil) @@ -50,10 +47,9 @@ def details(phone_number_id, fields: nil) # # @param phone_number_id [Integer] Phone Number Id. # @param params [Hash] Params to update. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the update was successful. def update(phone_number_id:, params:) - # this is a required field - params[:messaging_product] = 'whatsapp' + params[:messaging_product] = 'whatsapp' # messaging_products is a required field return raise InvalidVertical.new(vertical: params[:vertical]) unless valid_vertical?(params) response = send_request( @@ -62,10 +58,7 @@ def update(phone_number_id:, params:) params: params ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::SuccessResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end private diff --git a/lib/whatsapp_sdk/api/client.rb b/lib/whatsapp_sdk/api/client.rb index 3736039..59f9ad9 100644 --- a/lib/whatsapp_sdk/api/client.rb +++ b/lib/whatsapp_sdk/api/client.rb @@ -54,6 +54,10 @@ def send_request(endpoint: "", full_url: nil, http_method: "post", params: {}, h response = faraday_request.public_send(http_method, endpoint, request_params(params, headers), headers) + if response.status > 499 || Api::Responses::GenericErrorResponse.response_error?(response: response.body) + raise Api::Responses::HttpResponseError.new(http_status: response.status, body: JSON.parse(response.body)) + end + return nil if response.body == "" JSON.parse(response.body) diff --git a/lib/whatsapp_sdk/api/medias.rb b/lib/whatsapp_sdk/api/medias.rb index 75e3f0b..baff1bf 100644 --- a/lib/whatsapp_sdk/api/medias.rb +++ b/lib/whatsapp_sdk/api/medias.rb @@ -4,9 +4,6 @@ require "faraday/multipart" require_relative "request" -require_relative "response" -require_relative 'responses/media_data_response' -require_relative 'responses/success_response' require_relative '../resource/media_types' module WhatsappSdk @@ -37,17 +34,14 @@ def initialize(media_type:) # Get Media by ID. # # @param media_id [String] Media Id. - # @return [Api::Response] Response object. + # @return [Resource::Media] Media object. def get(media_id:) response = send_request( http_method: "get", endpoint: "/#{media_id}" ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MediaDataResponse - ) + Resource::Media.from_hash(response) end def media(media_id:) @@ -62,7 +56,7 @@ def media(media_id:) # @param media_type [String] The media type e.g. "audio/mp4". See possible types in the official # documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types, # but note that the API may allow more depending on the client. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the media was downloaded successfully. def download(url:, file_path:, media_type:) # Allow download of unsupported media types, since Cloud API may decide to let it through. # https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/discussions/127 @@ -71,17 +65,16 @@ def download(url:, file_path:, media_type:) content_type_header = map_media_type_to_content_type_header(media_type) response = download_file(url: url, file_path: file_path, content_type_header: content_type_header) - response = if response.code.to_i == 200 - { "success" => true } - else - { "error" => true, "status" => response.code } - end - - Api::Response.new( - response: response, - data_class_type: Api::Responses::SuccessResponse, - error_class_type: Api::Responses::ErrorResponse - ) + + return true if response.code.to_i == 200 + + begin + body = JSON.parse(response.body) + rescue JSON::ParserError + body = { "message" => response.body } + end + + raise Api::Responses::HttpResponseError.new(http_status: response.code, body: body) end # Upload a media. @@ -90,7 +83,7 @@ def download(url:, file_path:, media_type:) # @param type [String] Media type e.g. text/plain, video/3gp, image/jpeg, image/png. For more information, # see the official documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types. # - # @return [Api::Response] Response object. + # @return [Api::Responses::IdResponse] IdResponse object. def upload(sender_id:, file_path:, type:, headers: {}) raise FileNotFoundError.new(file_path: file_path) unless File.file?(file_path) @@ -108,26 +101,20 @@ def upload(sender_id:, file_path:, type:, headers: {}) multipart: true ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MediaDataResponse - ) + Api::Responses::IdResponse.new(response["id"]) end # Delete a Media by ID. # # @param media_id [String] Media Id. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the media was deleted successfully. def delete(media_id:) response = send_request( http_method: "delete", endpoint: "/#{media_id}" ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::SuccessResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end private diff --git a/lib/whatsapp_sdk/api/messages.rb b/lib/whatsapp_sdk/api/messages.rb index b6a0e84..1614b51 100644 --- a/lib/whatsapp_sdk/api/messages.rb +++ b/lib/whatsapp_sdk/api/messages.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "request" -require_relative "response" module WhatsappSdk module Api @@ -14,7 +13,7 @@ class Messages < Request # @param recipient_number [Integer] Recipient' Phone number. # @param message [String] Text to send. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_text(sender_id:, recipient_number:, message:, message_id: nil) params = { messaging_product: "whatsapp", @@ -31,10 +30,7 @@ def send_text(sender_id:, recipient_number:, message:, message_id: nil) headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send location. @@ -46,7 +42,7 @@ def send_text(sender_id:, recipient_number:, message:, message_id: nil) # @param name [String] Location name. # @param address [String] Location address. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_location( sender_id:, recipient_number:, longitude:, latitude:, name:, address:, message_id: nil ) @@ -70,10 +66,7 @@ def send_location( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send an image. @@ -84,7 +77,7 @@ def send_location( # @param link [String] Image link. # @param caption [String] Image caption. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_image( sender_id:, recipient_number:, image_id: nil, link: nil, caption: "", message_id: nil ) @@ -109,10 +102,7 @@ def send_image( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send an audio. @@ -122,7 +112,7 @@ def send_image( # @param audio_id [String] Audio ID. # @param link [String] Audio link. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_audio(sender_id:, recipient_number:, audio_id: nil, link: nil, message_id: nil) raise Resource::Errors::MissingArgumentError, "audio_id or link is required" if !audio_id && !link @@ -142,10 +132,7 @@ def send_audio(sender_id:, recipient_number:, audio_id: nil, link: nil, message_ multipart: true ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send a video. @@ -156,7 +143,7 @@ def send_audio(sender_id:, recipient_number:, audio_id: nil, link: nil, message_ # @param link [String] Image link. # @param caption [String] Image caption. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_video( sender_id:, recipient_number:, video_id: nil, link: nil, caption: "", message_id: nil ) @@ -181,10 +168,7 @@ def send_video( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send a document. @@ -195,7 +179,7 @@ def send_video( # @param link [String] Image link. # @param caption [String] Image caption. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_document( sender_id:, recipient_number:, document_id: nil, link: nil, caption: "", message_id: nil, filename: nil ) @@ -224,10 +208,7 @@ def send_document( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send a document. @@ -237,7 +218,7 @@ def send_document( # @param sticker_id [String] The sticker ID. # @param link [String] Image link. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_sticker(sender_id:, recipient_number:, sticker_id: nil, link: nil, message_id: nil) raise Resource::Errors::MissingArgumentError, "sticker or link is required" if !sticker_id && !link @@ -256,10 +237,7 @@ def send_sticker(sender_id:, recipient_number:, sticker_id: nil, link: nil, mess headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send contacts. @@ -270,7 +248,7 @@ def send_sticker(sender_id:, recipient_number:, sticker_id: nil, link: nil, mess # @param contacts [Array] Contacts. # @param contacts_json [Json] Contacts. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_contacts( sender_id:, recipient_number:, contacts: nil, contacts_json: {}, message_id: nil ) @@ -289,10 +267,7 @@ def send_contacts( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # def send_interactive_button @@ -309,7 +284,7 @@ def send_contacts( # @param interactive_json [Json] The interactive object as a Json. # If you pass interactive_json, you can't pass interactive. # @param message_id [String] The id of the message to reply to. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_interactive_message( sender_id:, recipient_number:, interactive: nil, interactive_json: nil, message_id: nil ) @@ -338,10 +313,7 @@ def send_interactive_message( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end alias send_interactive_reply_buttons send_interactive_message @@ -351,7 +323,7 @@ def send_interactive_message( # # @param sender_id [Integer] Sender' phone number. # @param message_id [Integer] Message ID. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the message was marked as read. def read_message(sender_id:, message_id:) params = { messaging_product: "whatsapp", @@ -365,10 +337,7 @@ def read_message(sender_id:, message_id:) headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::ReadMessageDataResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end # Send template @@ -379,7 +348,7 @@ def read_message(sender_id:, message_id:) # @param language [String] template language. # @param components [Component] Component. # @param components_json [Json] The component as a Json. If you pass components_json, you can't pass components. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_template( sender_id:, recipient_number:, name:, language:, components: nil, components_json: nil ) @@ -411,10 +380,7 @@ def send_template( headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end # Send reaction @@ -423,7 +389,7 @@ def send_template( # @param recipient_number [Integer] Recipient' Phone number. # @param message_id [String] the id of the message to reaction. # @param emoji [String] unicode of the emoji to send. - # @return [Api::Response] Response object. + # @return [MessageDataResponse] Response object. def send_reaction(sender_id:, recipient_number:, message_id:, emoji:) params = { messaging_product: "whatsapp", @@ -442,10 +408,7 @@ def send_reaction(sender_id:, recipient_number:, message_id:, emoji:) headers: DEFAULT_HEADERS ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::MessageDataResponse - ) + Api::Responses::MessageDataResponse.build_from_response(response: response) end private diff --git a/lib/whatsapp_sdk/api/phone_numbers.rb b/lib/whatsapp_sdk/api/phone_numbers.rb index 147a5b7..651482d 100644 --- a/lib/whatsapp_sdk/api/phone_numbers.rb +++ b/lib/whatsapp_sdk/api/phone_numbers.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "request" -require_relative "response" module WhatsappSdk module Api @@ -22,33 +21,31 @@ def list(business_id) endpoint: "#{business_id}/phone_numbers?fields=#{DEFAULT_FIELDS}" ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::PhoneNumbersDataResponse + Api::Responses::PaginationRecords.new( + records: parse_phone_numbers(response['data']), + before: response['paging']['cursors']['before'], + after: response['paging']['cursors']['after'] ) end # Get the registered number. # # @param phone_number_id [Integer] The registered number we want to retrieve. - # @return [Api::Response] Response object. + # @return [Resource::PhoneNumber] A PhoneNumber object. def get(phone_number_id) response = send_request( http_method: "get", endpoint: "#{phone_number_id}?fields=#{DEFAULT_FIELDS}" ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::PhoneNumberDataResponse - ) + Resource::PhoneNumber.from_hash(response) end # Register a phone number. # # @param phone_number_id [Integer] The registered number we want to retrieve. # @param pin [Integer] Pin of 6 digits. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the registration was successful. def register_number(phone_number_id, pin) response = send_request( http_method: "post", @@ -56,16 +53,13 @@ def register_number(phone_number_id, pin) params: { messaging_product: 'whatsapp', pin: pin } ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::PhoneNumberDataResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end # Deregister a phone number. # # @param phone_number_id [Integer] The registered number we want to retrieve. - # @return [Api::Response] Response object. + # @return [Boolean] Whether the deregistration was successful. def deregister_number(phone_number_id) response = send_request( http_method: "post", @@ -73,10 +67,7 @@ def deregister_number(phone_number_id) params: {} ) - Api::Response.new( - response: response, - data_class_type: Api::Responses::PhoneNumberDataResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end # deprecated methods @@ -89,6 +80,14 @@ def registered_number(phone_number_id) warn "[DEPRECATION] `registered_number` is deprecated. Please use `get` instead." get(phone_number_id) end + + private + + def parse_phone_numbers(phone_numbers_data) + phone_numbers_data.map do |phone_number| + Resource::PhoneNumber.from_hash(phone_number) + end + end end end end diff --git a/lib/whatsapp_sdk/api/response.rb b/lib/whatsapp_sdk/api/response.rb deleted file mode 100644 index d9563f8..0000000 --- a/lib/whatsapp_sdk/api/response.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require_relative "responses/message_data_response" -require_relative "responses/phone_number_data_response" -require_relative "responses/phone_numbers_data_response" -require_relative "responses/read_message_data_response" -require_relative "responses/message_error_response" -require_relative "responses/business_profile_data_response" - -module WhatsappSdk - module Api - class Response - attr_accessor :error, :data, :raw_response - - def initialize(response:, data_class_type:, error_class_type: Responses::MessageErrorResponse) - @raw_response = response - @data = data_class_type.build_from_response(response: response) - @error = error_class_type.build_from_response(response: response) - end - - # @return [Boolean] Whether or not the response is successful. - def ok? - @error.nil? - end - - # @return [Boolean] Whether or not the response has an error. - def error? - !!@error - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/business_profile_data_response.rb b/lib/whatsapp_sdk/api/responses/business_profile_data_response.rb deleted file mode 100644 index 6c6133d..0000000 --- a/lib/whatsapp_sdk/api/responses/business_profile_data_response.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" - -module WhatsappSdk - module Api - module Responses - class BusinessProfileDataResponse < DataResponse - attr_accessor :about, :address, :description, :email, :messaging_product, - :profile_picture_url, :vertical, :websites - - def initialize(response) - @about = response["data"][0]["about"] - @address = response["data"][0]["address"] - @description = response["data"][0]["description"] - @email = response["data"][0]["email"] - @messaging_product = response["data"][0]["messaging_product"] - @profile_picture_url = response["data"][0]["profile_picture_url"] - @vertical = response["data"][0]["vertical"] - @websites = response["data"][0]["websites"] - - super(response) - end - - def self.build_from_response(response:) - return nil if response['error'] - - new(response) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/data_response.rb b/lib/whatsapp_sdk/api/responses/data_response.rb deleted file mode 100644 index e115d76..0000000 --- a/lib/whatsapp_sdk/api/responses/data_response.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module WhatsappSdk - module Api - module Responses - class DataResponse - attr_reader :raw_data_response - - def initialize(response) - @raw_data_response = response - end - - def self.build_from_response(response:); end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/error_response.rb b/lib/whatsapp_sdk/api/responses/error_response.rb deleted file mode 100644 index f628c05..0000000 --- a/lib/whatsapp_sdk/api/responses/error_response.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" - -module WhatsappSdk - module Api - module Responses - class ErrorResponse < DataResponse - attr_accessor :error, :status - - def initialize(response:) - @error = response["error"] - @status = response["status"] - super(response) - end - - def self.build_from_response(response:) - return unless response["error"] - - new(response: response) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/generic_error_response.rb b/lib/whatsapp_sdk/api/responses/generic_error_response.rb index 2c1ef1f..04712f0 100644 --- a/lib/whatsapp_sdk/api/responses/generic_error_response.rb +++ b/lib/whatsapp_sdk/api/responses/generic_error_response.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require_relative "error_response" - module WhatsappSdk module Api module Responses - class GenericErrorResponse < ErrorResponse + class GenericErrorResponse attr_reader :code, :subcode, :message, :type, :data, :fbtrace_id def initialize(response:) @@ -15,8 +13,10 @@ def initialize(response:) @type = response["type"] @data = response["data"] @fbtrace_id = response["fbtrace_id"] + end - super(response: response) + def self.response_error?(response:) + response["error"] end def self.build_from_response(response:) diff --git a/lib/whatsapp_sdk/api/responses/http_response_error.rb b/lib/whatsapp_sdk/api/responses/http_response_error.rb new file mode 100644 index 0000000..fa161a1 --- /dev/null +++ b/lib/whatsapp_sdk/api/responses/http_response_error.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module WhatsappSdk + module Api + module Responses + class HttpResponseError < StandardError + attr_reader :http_status, :body, :error_info + + def initialize(http_status:, body:) + super + @http_status = http_status + @body = body + @error_info = GenericErrorResponse.build_from_response(response: body) + end + end + end + end +end diff --git a/lib/whatsapp_sdk/api/responses/message_error_response.rb b/lib/whatsapp_sdk/api/responses/id_response.rb similarity index 51% rename from lib/whatsapp_sdk/api/responses/message_error_response.rb rename to lib/whatsapp_sdk/api/responses/id_response.rb index b804595..281fe98 100644 --- a/lib/whatsapp_sdk/api/responses/message_error_response.rb +++ b/lib/whatsapp_sdk/api/responses/id_response.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true -require_relative "generic_error_response" - module WhatsappSdk module Api module Responses - class MessageErrorResponse < GenericErrorResponse + class IdResponse + attr_accessor :id + + def initialize(id) + @id = id + end end end end diff --git a/lib/whatsapp_sdk/api/responses/media_data_response.rb b/lib/whatsapp_sdk/api/responses/media_data_response.rb deleted file mode 100644 index 6f10d47..0000000 --- a/lib/whatsapp_sdk/api/responses/media_data_response.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" - -module WhatsappSdk - module Api - module Responses - class MediaDataResponse < DataResponse - attr_accessor :id, :url, :mime_type, :sha256, :file_size, :messaging_product - - def initialize(response) - @id = response["id"] - @messaging_product = response["messaging_product"] - @url = response["url"] - @mime_type = response["mime_type"] - @sha256 = response["sha256"] - @file_size = response["file_size"] - - super(response) - end - - def self.build_from_response(response:) - return unless response["id"] - - new(response) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/message_data_response.rb b/lib/whatsapp_sdk/api/responses/message_data_response.rb index 1552c7a..3fee5db 100644 --- a/lib/whatsapp_sdk/api/responses/message_data_response.rb +++ b/lib/whatsapp_sdk/api/responses/message_data_response.rb @@ -1,37 +1,34 @@ # frozen_string_literal: true -require_relative "../request" -require_relative "data_response" require_relative "../../resource/message" require_relative "../../resource/contact_response" module WhatsappSdk module Api module Responses - class MessageDataResponse < DataResponse - attr_reader :contacts, :messages + class MessageDataResponse + attr_accessor :contacts, :messages - def initialize(response:) - @contacts = response["contacts"]&.map { |contact_json| parse_contact(contact_json) } - @messages = response["messages"]&.map { |contact_json| parse_message(contact_json) } + class << self + def build_from_response(response:) + return unless response["messages"] - super(response) - end - - def self.build_from_response(response:) - return unless response["messages"] + data_response = new + data_response.contacts = response["contacts"]&.map { |contact_json| parse_contact(contact_json) } + data_response.messages = response["messages"]&.map { |contact_json| parse_message(contact_json) } - new(response: response) - end + data_response + end - private + private - def parse_message(message_json) - Resource::Message.new(id: message_json["id"]) - end + def parse_message(message_json) + Resource::Message.new(id: message_json["id"]) + end - def parse_contact(contact_json) - Resource::ContactResponse.new(input: contact_json["input"], wa_id: contact_json["wa_id"]) + def parse_contact(contact_json) + Resource::ContactResponse.new(input: contact_json["input"], wa_id: contact_json["wa_id"]) + end end end end diff --git a/lib/whatsapp_sdk/api/responses/message_template_namespace_data_response.rb b/lib/whatsapp_sdk/api/responses/message_template_namespace_data_response.rb deleted file mode 100644 index faef275..0000000 --- a/lib/whatsapp_sdk/api/responses/message_template_namespace_data_response.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" -require_relative "../../resource/template" - -module WhatsappSdk - module Api - module Responses - class MessageTemplateNamespaceDataResponse < DataResponse - attr_accessor :message_template_namespace, :id - - def initialize(response) - @id = response["id"] - @message_template_namespace = response["message_template_namespace"] - - super(response) - end - - def self.build_from_response(response:) - return unless response["id"] - - new(response) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/pagination_records.rb b/lib/whatsapp_sdk/api/responses/pagination_records.rb new file mode 100644 index 0000000..db2fcba --- /dev/null +++ b/lib/whatsapp_sdk/api/responses/pagination_records.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module WhatsappSdk + module Api + module Responses + class PaginationRecords + attr_reader :records, :before, :after + + def initialize(records:, before:, after:) + @records = records + @before = before + @after = after + end + end + end + end +end diff --git a/lib/whatsapp_sdk/api/responses/phone_number_data_response.rb b/lib/whatsapp_sdk/api/responses/phone_number_data_response.rb deleted file mode 100644 index f438c39..0000000 --- a/lib/whatsapp_sdk/api/responses/phone_number_data_response.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" - -module WhatsappSdk - module Api - module Responses - class PhoneNumberDataResponse < DataResponse - attr_accessor :id, :verified_name, :display_phone_number, :quality_rating, :is_pin_enabled, - :is_official_business_account, :account_mode, :certificate, :code_verification_status, - :eligibility_for_api_business_global_search, :name_status, :new_name_status, :status, - :search_visibility, :messaging_limit_tier - - def initialize(response) - @id = response["id"] - @verified_name = response["verified_name"] - @display_phone_number = response["display_phone_number"] - @quality_rating = response["quality_rating"] - @is_pin_enabled = response["is_pin_enabled"] - @is_official_business_account = response["is_official_business_account"] - @account_mode = response["account_mode"] - @certificate = response["certificate"] - @code_verification_status = response["code_verification_status"] - @eligibility_for_api_business_global_search = response["eligibility_for_api_business_global_search"] - @name_status = response["name_status"] - @new_name_status = response["new_name_status"] - @status = response["status"] - @search_visibility = response["search_visibility"] - @messaging_limit_tier = response["messaging_limit_tier"] - - super(response) - end - - def self.build_from_response(response:) - return unless response["id"] - - new(response) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/phone_numbers_data_response.rb b/lib/whatsapp_sdk/api/responses/phone_numbers_data_response.rb deleted file mode 100644 index 3f490df..0000000 --- a/lib/whatsapp_sdk/api/responses/phone_numbers_data_response.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" -require_relative "phone_number_data_response" - -module WhatsappSdk - module Api - module Responses - class PhoneNumbersDataResponse < DataResponse - attr_reader :phone_numbers - - def initialize(response) - @phone_numbers = response['data']&.map { |phone_number| parse_phone_number(phone_number) } - - super(response) - end - - def self.build_from_response(response:) - return unless response["data"] - - new(response) - end - - private - - def parse_phone_number(phone_number) - PhoneNumberDataResponse.new(phone_number) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/read_message_data_response.rb b/lib/whatsapp_sdk/api/responses/read_message_data_response.rb deleted file mode 100644 index fc4c3de..0000000 --- a/lib/whatsapp_sdk/api/responses/read_message_data_response.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require_relative "../request" -require_relative "data_response" -require_relative "../../resource/message" -require_relative "../../resource/contact_response" - -module WhatsappSdk - module Api - module Responses - class ReadMessageDataResponse < DataResponse - def initialize(response:) - @success = response["success"] - super(response) - end - - def self.build_from_response(response:) - return if response["error"] - - new(response: response) - end - - def success? - @success - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/success_response.rb b/lib/whatsapp_sdk/api/responses/success_response.rb index 26289fd..4fc1b2c 100644 --- a/lib/whatsapp_sdk/api/responses/success_response.rb +++ b/lib/whatsapp_sdk/api/responses/success_response.rb @@ -1,24 +1,11 @@ # frozen_string_literal: true -require_relative "data_response" - module WhatsappSdk module Api module Responses - class SuccessResponse < DataResponse - def initialize(response:) - @success = response["success"] - super(response) - end - - def self.build_from_response(response:) - return unless response["success"] - - new(response: response) - end - - def success? - @success + class SuccessResponse + def self.success_response?(response:) + response["success"] == true end end end diff --git a/lib/whatsapp_sdk/api/responses/template_data_response.rb b/lib/whatsapp_sdk/api/responses/template_data_response.rb deleted file mode 100644 index 333a0e7..0000000 --- a/lib/whatsapp_sdk/api/responses/template_data_response.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" -require_relative "../../resource/template" - -module WhatsappSdk - module Api - module Responses - class TemplateDataResponse < DataResponse - attr_reader :template - - def initialize(response:) - @template = parse_template(response) - - super(response) - end - - def self.build_from_response(response:) - return unless response["id"] - - new(response: response) - end - - private - - def parse_template(template_json) - status = template_json["status"] - category = template_json["category"] - id = template_json["id"] - language = template_json["language"] - name = template_json["name"] - components_json = template_json["components"] - - ::WhatsappSdk::Resource::Template.new( - id: id, - status: status, - category: category, - language: language, - name: name, - components_json: components_json - ) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/responses/templates_data_response.rb b/lib/whatsapp_sdk/api/responses/templates_data_response.rb deleted file mode 100644 index 49435ea..0000000 --- a/lib/whatsapp_sdk/api/responses/templates_data_response.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require_relative "data_response" -require_relative "template_data_response" - -module WhatsappSdk - module Api - module Responses - class TemplatesDataResponse < DataResponse - attr_reader :templates - - def initialize(response) - @templates = response['data']&.map { |template| parse_templates(template) } - - super(response) - end - - def self.build_from_response(response:) - return unless response["data"] - - new(response) - end - - private - - def parse_templates(template) - TemplateDataResponse.new(response: template) - end - end - end - end -end diff --git a/lib/whatsapp_sdk/api/templates.rb b/lib/whatsapp_sdk/api/templates.rb index 4767188..2fb2788 100644 --- a/lib/whatsapp_sdk/api/templates.rb +++ b/lib/whatsapp_sdk/api/templates.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true require_relative "request" -require_relative "response" -require_relative 'responses/success_response' -require_relative 'responses/message_template_namespace_data_response' -require_relative 'responses/generic_error_response' -require_relative 'responses/template_data_response' -require_relative 'responses/templates_data_response' require_relative "../resource/languages" module WhatsappSdk @@ -36,7 +30,7 @@ def initialize(category:) # @param allow_category_change [Boolean] Optional Allow category change. # Set to true to allow us to assign a category based on the template guidelines and the template's contents. # This can prevent your template from being rejected for miscategorization. - # @return [Response] Response object. + # @return [Template] Template object. def create( business_id:, name:, category:, language:, components_json: nil, allow_category_change: nil ) @@ -63,18 +57,14 @@ def create( headers: DEFAULT_HEADERS ) - Response.new( - response: response, - data_class_type: Responses::TemplateDataResponse, - error_class_type: Responses::GenericErrorResponse - ) + Resource::Template.from_hash(response) end # Get templates # # @param business_id [Integer] The business ID. # @param limit [Integer] Optional. Number of templates to return in a single page. - # @return [Response] Response object. + # @return [Template] Pagination Record containing an array of Template objects. def list(business_id:, limit: 100) params = {} params["limit"] = limit if limit @@ -85,10 +75,10 @@ def list(business_id:, limit: 100) params: params ) - Response.new( - response: response, - data_class_type: Responses::TemplatesDataResponse, - error_class_type: Responses::GenericErrorResponse + Api::Responses::PaginationRecords.new( + records: parse_templates(response['data']), + before: response['paging']['cursors']['before'], + after: response['paging']['cursors']['after'] ) end @@ -101,7 +91,7 @@ def templates(business_id:) # The message template namespace is required to send messages using the message templates. # # @param business_id [Integer] The business ID. - # @return [Response] Response object. + # @return [MessageTemplateNamespace] MessageTemplateNamespace object. def get_message_template_namespace(business_id:) response = send_request( endpoint: business_id.to_s, @@ -109,11 +99,7 @@ def get_message_template_namespace(business_id:) params: { "fields" => "message_template_namespace" } ) - Response.new( - response: response, - data_class_type: Responses::MessageTemplateNamespaceDataResponse, - error_class_type: Responses::GenericErrorResponse - ) + WhatsappSdk::Resource::MessageTemplateNamespace.from_hash(response) end # Edit Template @@ -125,7 +111,7 @@ def get_message_template_namespace(business_id:) # # @param id [String] Required. The message_template-id. # @param components_json [Json] Components that make up the template.. - # return [Response] Response object. + # @return [Boolean] Whether the update was successful. def update(template_id:, category: nil, components_json: nil) if category && !WhatsappSdk::Resource::Template::Category.valid?(category) raise InvalidCategoryError.new(category: category) @@ -142,11 +128,7 @@ def update(template_id:, category: nil, components_json: nil) headers: { "Content-Type" => "application/json" } ) - Response.new( - response: response, - data_class_type: Responses::SuccessResponse, - error_class_type: Responses::GenericErrorResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) end # Delete Template @@ -160,7 +142,7 @@ def update(template_id:, category: nil, components_json: nil) # @param name [String] Required. The template's name. # @param hsm_id [String] Optional. The template's id. # - # @return [Response] Response object. + # @return [Boolean] Whether the deletion was successful. def delete(business_id:, name:, hsm_id: nil) params = { name: name } params[:hsm_id] = hsm_id if hsm_id @@ -171,11 +153,15 @@ def delete(business_id:, name:, hsm_id: nil) params: params ) - Response.new( - response: response, - data_class_type: Responses::SuccessResponse, - error_class_type: Responses::GenericErrorResponse - ) + Api::Responses::SuccessResponse.success_response?(response: response) + end + + private + + def parse_templates(templates_data) + templates_data.map do |template| + Resource::Template.from_hash(template) + end end end end diff --git a/lib/whatsapp_sdk/resource/business_profile.rb b/lib/whatsapp_sdk/resource/business_profile.rb index d8c72b3..13b3ddb 100644 --- a/lib/whatsapp_sdk/resource/business_profile.rb +++ b/lib/whatsapp_sdk/resource/business_profile.rb @@ -7,6 +7,34 @@ class BusinessProfile UNDEFINED OTHER AUTO BEAUTY APPAREL EDU ENTERTAIN EVENT_PLAN FINANCE GROCERY GOVT HOTEL HEALTH NONPROFIT PROF_SERVICES RETAIL TRAVEL RESTAURANT NOT_A_BIZ ].freeze + + attr_accessor :about, :address, :description, :email, :messaging_product, + :profile_picture_url, :vertical, :websites + + def self.from_hash(hash) + business_profile = BusinessProfile.new + business_profile.about = hash["about"] + business_profile.address = hash["address"] + business_profile.description = hash["description"] + business_profile.email = hash["email"] + business_profile.messaging_product = hash["messaging_product"] + business_profile.profile_picture_url = hash["profile_picture_url"] + business_profile.vertical = hash["vertical"] + business_profile.websites = hash["websites"] + + business_profile + end + + def ==(other) + about == other.about && + address == other.address && + description == other.description && + email == other.email && + messaging_product == other.messaging_product && + profile_picture_url == other.profile_picture_url && + vertical == other.vertical && + websites == other.websites + end end end end diff --git a/lib/whatsapp_sdk/resource/media.rb b/lib/whatsapp_sdk/resource/media.rb index df95128..b1fef64 100644 --- a/lib/whatsapp_sdk/resource/media.rb +++ b/lib/whatsapp_sdk/resource/media.rb @@ -3,21 +3,6 @@ module WhatsappSdk module Resource class Media - class InvalidMedia < StandardError - attr_reader :field, :message - - def initialize(field, message) - @field = field - @message = message - super(message) - end - end - - # Returns media id. - # - # @returns id [String]. - attr_accessor :id - module Type AUDIO = 'audio' DOCUMENT = 'document' @@ -26,61 +11,26 @@ module Type STICKER = 'sticker' end - # @returns type [String]. Valid options ar audio, document, image, video and sticker. - attr_accessor :type - - # The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs. - # Do not use this field when the message type is set to text. - # - # @returns link [String]. - attr_accessor :link - - # Describes the specified document or image media. - # - # @returns caption [String]. - attr_accessor :caption - - # Describes the filename for the specific document. Use only with document media. - # - # @returns filename [String]. - attr_accessor :filename - - def initialize(type:, id: nil, link: nil, caption: nil, filename: nil) - @type = type - @id = id - @link = link - @caption = caption - @filename = filename - validate_media - end - - def to_json - json = {} - json[:id] = id unless id.nil? - json[:link] = link unless link.nil? - json[:caption] = caption unless caption.nil? - json[:filename] = filename unless filename.nil? - json - end - - private - - def validate_media - raise InvalidMedia.new(:filename, "filename can only be used with document") if filename && !supports_filename? - - if caption && !supports_caption? - raise InvalidMedia.new(:caption, "caption can only be used with document or image") - end - - true - end - - def supports_filename? - type == Type::DOCUMENT + attr_accessor :file_size, :id, :messaging_product, :mime_type, :sha256, :url + + def self.from_hash(hash) + media = new + media.id = hash["id"] + media.file_size = hash["file_size"] + media.messaging_product = hash["messaging_product"] + media.mime_type = hash["mime_type"] + media.sha256 = hash["sha256"] + media.url = hash["url"] + media end - def supports_caption? - type == Type::DOCUMENT || type == Type::IMAGE + def ==(other) + id == other.id && + file_size == other.file_size && + messaging_product == other.messaging_product && + mime_type == other.mime_type && + sha256 == other.sha256 && + url == other.url end end end diff --git a/lib/whatsapp_sdk/resource/media_component.rb b/lib/whatsapp_sdk/resource/media_component.rb new file mode 100644 index 0000000..a04f5cd --- /dev/null +++ b/lib/whatsapp_sdk/resource/media_component.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# The media used by a component (button, header, body, footer) etc. + +module WhatsappSdk + module Resource + class MediaComponent + class InvalidMedia < StandardError + attr_reader :field, :message + + def initialize(field, message) + @field = field + @message = message + super(message) + end + end + + # Returns media id. + # + # @returns id [String]. + attr_accessor :id + + module Type + AUDIO = 'audio' + DOCUMENT = 'document' + IMAGE = 'image' + VIDEO = 'video' + STICKER = 'sticker' + end + + # @returns type [String]. Valid options ar audio, document, image, video and sticker. + attr_accessor :type + + # The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs. + # Do not use this field when the message type is set to text. + # + # @returns link [String]. + attr_accessor :link + + # Describes the specified document or image media. + # + # @returns caption [String]. + attr_accessor :caption + + # Describes the filename for the specific document. Use only with document media. + # + # @returns filename [String]. + attr_accessor :filename + + def initialize(type:, id: nil, link: nil, caption: nil, filename: nil) + @type = type + @id = id + @link = link + @caption = caption + @filename = filename + validate_media + end + + def to_json + json = {} + json[:id] = id unless id.nil? + json[:link] = link unless link.nil? + json[:caption] = caption unless caption.nil? + json[:filename] = filename unless filename.nil? + json + end + + private + + def validate_media + raise InvalidMedia.new(:filename, "filename can only be used with document") if filename && !supports_filename? + + if caption && !supports_caption? + raise InvalidMedia.new(:caption, "caption can only be used with document or image") + end + + true + end + + def supports_filename? + type == Type::DOCUMENT + end + + def supports_caption? + type == Type::DOCUMENT || type == Type::IMAGE + end + end + end +end diff --git a/lib/whatsapp_sdk/resource/message_template_namespace.rb b/lib/whatsapp_sdk/resource/message_template_namespace.rb new file mode 100644 index 0000000..bc864af --- /dev/null +++ b/lib/whatsapp_sdk/resource/message_template_namespace.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module WhatsappSdk + module Resource + class MessageTemplateNamespace + attr_accessor :id, :message_template_namespace + + def self.from_hash(hash) + message_template_namespace = new + message_template_namespace.id = hash["id"] + message_template_namespace.message_template_namespace = hash["message_template_namespace"] + + message_template_namespace + end + end + end +end diff --git a/lib/whatsapp_sdk/resource/phone_number.rb b/lib/whatsapp_sdk/resource/phone_number.rb index 93243cb..6c4dda2 100644 --- a/lib/whatsapp_sdk/resource/phone_number.rb +++ b/lib/whatsapp_sdk/resource/phone_number.rb @@ -3,21 +3,51 @@ module WhatsappSdk module Resource class PhoneNumber - attr_accessor :phone, :wa_id, :type + attr_accessor :id, :verified_name, :display_phone_number, :quality_rating, :is_pin_enabled, + :is_official_business_account, :account_mode, :certificate, :code_verification_status, + :eligibility_for_api_business_global_search, :name_status, :new_name_status, :status, + :search_visibility, :messaging_limit_tier # , :phone, :wa_id, :type - def initialize(phone:, type:, wa_id:) - @phone = phone - @type = type - @wa_id = wa_id + def self.from_hash(hash) + phone_number = PhoneNumber.new + phone_number.id = hash["id"] + phone_number.verified_name = hash["verified_name"] + phone_number.display_phone_number = hash["display_phone_number"] + phone_number.quality_rating = hash["quality_rating"] + phone_number.is_pin_enabled = hash["is_pin_enabled"] + phone_number.is_official_business_account = hash["is_official_business_account"] + phone_number.account_mode = hash["account_mode"] + phone_number.certificate = hash["certificate"] + phone_number.code_verification_status = hash["code_verification_status"] + phone_number.eligibility_for_api_business_global_search = hash["eligibility_for_api_business_global_search"] + phone_number.name_status = hash["name_status"] + phone_number.new_name_status = hash["new_name_status"] + phone_number.status = hash["status"] + phone_number.search_visibility = hash["search_visibility"] + phone_number.messaging_limit_tier = hash["messaging_limit_tier"] + + phone_number end - def to_h - { - phone: @phone, - type: @type, - wa_id: @wa_id - } + # rubocop:disable Metrics/PerceivedComplexity + def ==(other) + id == other.id && + verified_name == other.verified_name && + display_phone_number == other.display_phone_number && + quality_rating == other.quality_rating && + is_pin_enabled == other.is_pin_enabled && + is_official_business_account == other.is_official_business_account && + account_mode == other.account_mode && + certificate == other.certificate && + code_verification_status == other.code_verification_status && + eligibility_for_api_business_global_search == other.eligibility_for_api_business_global_search && + name_status == other.name_status && + new_name_status == other.new_name_status && + status == other.status && + search_visibility == other.search_visibility && + messaging_limit_tier == other.messaging_limit_tier end + # rubocop:enable Metrics/PerceivedComplexity end end end diff --git a/lib/whatsapp_sdk/resource/phone_number_component.rb b/lib/whatsapp_sdk/resource/phone_number_component.rb new file mode 100644 index 0000000..1998e64 --- /dev/null +++ b/lib/whatsapp_sdk/resource/phone_number_component.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# The media used by a component (button, header, body, footer) etc. + +module WhatsappSdk + module Resource + class PhoneNumberComponent + attr_accessor :phone, :wa_id, :type + + def initialize(phone:, type:, wa_id:) + @phone = phone + @type = type + @wa_id = wa_id + end + + def to_h + { + phone: @phone, + type: @type, + wa_id: @wa_id + } + end + end + end +end diff --git a/lib/whatsapp_sdk/resource/template.rb b/lib/whatsapp_sdk/resource/template.rb index fbc22ba..2883093 100644 --- a/lib/whatsapp_sdk/resource/template.rb +++ b/lib/whatsapp_sdk/resource/template.rb @@ -46,6 +46,26 @@ def initialize(id:, status:, category:, language: nil, name: nil, components_jso @name = name @components_json = components_json end + + def self.from_hash(hash) + new( + id: hash["id"], + status: hash["status"], + category: hash["category"], + language: hash["language"], + name: hash["name"], + components_json: hash["components"] + ) + end + + def ==(other) + id == other.id && + status == other.status && + category == other.category && + language == other.language && + name == other.name && + components_json == other.components_json + end end end end diff --git a/test/api_response_helper.rb b/test/api_response_helper.rb deleted file mode 100644 index 4c3c554..0000000 --- a/test/api_response_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# typed: false -# frozen_string_literal: true - -require_relative '../lib/whatsapp_sdk/api/response' -require_relative '../lib/whatsapp_sdk/api/responses/success_response' - -module ApiResponseHelper - def assert_ok_response(response) - assert_equal(WhatsappSdk::Api::Response, response.class) - assert_nil(response.error) - assert_predicate(response, :ok?) - end - - def assert_ok_success_response(response) - assert_ok_response(response) - assert_equal(WhatsappSdk::Api::Responses::SuccessResponse, response.data.class) - assert_predicate(response.data, :success?) - end -end diff --git a/test/errors_helper.rb b/test/errors_helper.rb index 52c907e..5d91489 100644 --- a/test/errors_helper.rb +++ b/test/errors_helper.rb @@ -16,6 +16,39 @@ def assert_unsupported_request_error(http_method, response, object_id, fb_trace_ ) end + def assert_unsupported_request_error_v2( + http_method, object_id, fb_trace_id, error_info, type = "GraphMethodException" + ) + assert_error_info( + { + code: 100, + error_subcode: 33, + type: type, + message: "Unsupported #{http_method} request. Object with ID '#{object_id}' does not exist, " \ + "cannot be loaded due to missing permissions, or does not support this operation. " \ + "Please read the Graph API documentation at https://developers.facebook.com/docs/graph-api", + fbtrace_id: fb_trace_id + }, + error_info + ) + end + + def assert_error_info(expected_error, error) + [ + [expected_error[:message], error.message], + [expected_error[:type], error.type], + [expected_error[:code], error.code], + [expected_error[:error_subcode], error.subcode], + [expected_error[:fbtrace_id], error.fbtrace_id] + ].each do |expected, actual| + if expected.nil? + assert_nil(actual) + else + assert_equal(expected, actual) + end + end + end + def assert_error_response(expected_error, response) refute_predicate(response, :ok?) assert_nil(response.data) diff --git a/test/fixtures/vcr_cassettes/business_profile/details_sends_all_fields_by_default.yml b/test/fixtures/vcr_cassettes/business_profile/details_queries_all_fields_by_default.yml similarity index 68% rename from test/fixtures/vcr_cassettes/business_profile/details_sends_all_fields_by_default.yml rename to test/fixtures/vcr_cassettes/business_profile/details_queries_all_fields_by_default.yml index 91f15bd..0636f43 100644 --- a/test/fixtures/vcr_cassettes/business_profile/details_sends_all_fields_by_default.yml +++ b/test/fixtures/vcr_cassettes/business_profile/details_queries_all_fields_by_default.yml @@ -21,7 +21,7 @@ http_interactions: message: OK headers: Etag: - - '"ff7e3a480c4940bc7cd930619b52da7eab33e5a9"' + - '"639f2b55e7e775a81f9ac478c53bda9d7b9cf42a"' Content-Type: - application/json; charset=UTF-8 Vary: @@ -43,26 +43,27 @@ http_interactions: Expires: - Sat, 01 Jan 2000 00:00:00 GMT X-Fb-Request-Id: - - AREiev3zf-tBxuxOEg8tkSA + - AHV5GRs2FxI8_C0081bLMIG X-Fb-Trace-Id: - - FulkE3XEytI + - E2/ARmWJl6n X-Fb-Rev: - - '1017309649' + - '1017351937' X-Fb-Debug: - - G++E9iMjpovfb0Dl76MJvQQeyaaNpZE172ifAnGqPkqp6OGDPTFwBUzpqqC8ba9URE5zYBwJ36FHCZ5FSRSvXg== + - myju4daJEQ0FHTPgSVRJ/ZgHoXFy1o3lCtYgp3HrYLYvA98PQ7ChfkhZgb16ivfMJ8va4mTjebos8ho6ny2A5Q== Date: - - Mon, 14 Oct 2024 03:39:48 GMT + - Tue, 15 Oct 2024 17:04:02 GMT X-Fb-Connection-Quality: - - EXCELLENT; q=0.9, rtt=27, rtx=0, c=10, mss=1380, tbw=3370, tp=-1, tpl=-1, - uplat=244, ullat=0 + - EXCELLENT; q=0.9, rtt=30, rtx=0, c=10, mss=1380, tbw=3367, tp=-1, tpl=-1, + uplat=463, ullat=0 Alt-Svc: - h3=":443"; ma=86400 Connection: - keep-alive Content-Length: - - '66' + - '254' body: encoding: UTF-8 - string: '{"data":[{"vertical":"UNDEFINED","messaging_product":"whatsapp"}]}' - recorded_at: Mon, 14 Oct 2024 03:39:48 GMT + string: '{"data":[{"about":"A very cool business","address":"123, Main Street, + New York, NY, 10001","description":"This is a description","email":"testing\u0040gmail.com","websites":["https:\/\/www.google.com\/"],"vertical":"EDU","messaging_product":"whatsapp"}]}' + recorded_at: Tue, 15 Oct 2024 17:04:02 GMT recorded_with: VCR 6.3.1 diff --git a/test/fixtures/vcr_cassettes/business_profile/details_with_success_response.yml b/test/fixtures/vcr_cassettes/business_profile/details_with_success_response.yml deleted file mode 100644 index 0574982..0000000 --- a/test/fixtures/vcr_cassettes/business_profile/details_with_success_response.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://graph.facebook.com/v19.0/107878721936019/whatsapp_business_profile?fields=about,address,description,email,profile_picture_url,websites,vertical - body: - encoding: US-ASCII - string: '' - headers: - Authorization: - - Bearer - User-Agent: - - Faraday v2.12.0 - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - response: - status: - code: 200 - message: OK - headers: - Etag: - - '"538be1ffb9eb33e2bc64f736ec6b6992f191c877"' - Content-Type: - - application/json; charset=UTF-8 - Vary: - - Origin - X-Ad-Api-Version-Warning: - - The call has been auto-upgraded to v20.0 as v19.0 will be deprecated. - X-Business-Use-Case-Usage: - - '{"114503234599312":[{"type":"whatsapp_business_management","call_count":1,"total_cputime":1,"total_time":1,"estimated_time_to_regain_access":0}]}' - Access-Control-Allow-Origin: - - "*" - Facebook-Api-Version: - - v20.0 - Strict-Transport-Security: - - max-age=15552000; preload - Pragma: - - no-cache - Cache-Control: - - private, no-cache, no-store, must-revalidate - Expires: - - Sat, 01 Jan 2000 00:00:00 GMT - X-Fb-Request-Id: - - AA2Khe_uGvs25oWus0KgCV8 - X-Fb-Trace-Id: - - GsodfJbKyu+ - X-Fb-Rev: - - '1017309649' - X-Fb-Debug: - - S24OQBhq1LhUjqUIfJcjyIW7ScfrbRHaV6YzTocP5R20T6k7oNUvKuY94eyrIXd5kK5ESGu8iL9GEsBMLDe5PA== - Date: - - Mon, 14 Oct 2024 03:59:34 GMT - X-Fb-Connection-Quality: - - EXCELLENT; q=0.9, rtt=26, rtx=0, c=10, mss=1380, tbw=3369, tp=-1, tpl=-1, - uplat=315, ullat=0 - Alt-Svc: - - h3=":443"; ma=86400 - Connection: - - keep-alive - Content-Length: - - '223' - body: - encoding: UTF-8 - string: '{"data":[{"address":"123, Main Street, New York, NY, 10001","description":"This - is a description","email":"testing\u0040gmail.com","websites":["https:\/\/www.google.com\/"],"vertical":"EDU","messaging_product":"whatsapp"}]}' - recorded_at: Mon, 14 Oct 2024 03:59:34 GMT -recorded_with: VCR 6.3.1 diff --git a/test/fixtures/vcr_cassettes/medias/test_get_document_with_success_response.yml b/test/fixtures/vcr_cassettes/medias/test_get_document_with_success_response.yml new file mode 100644 index 0000000..d507768 --- /dev/null +++ b/test/fixtures/vcr_cassettes/medias/test_get_document_with_success_response.yml @@ -0,0 +1,67 @@ +--- +http_interactions: +- request: + method: get + uri: https://graph.facebook.com/396305933546205 + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - Bearer + User-Agent: + - Faraday v2.12.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Etag: + - '"1f30aa67e648d76daa8041643b4bde9ee2ea0660"' + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Accept-Encoding + - Origin + X-Business-Use-Case-Usage: + - '{"114503234599312":[{"type":"whatsapp","call_count":1,"total_cputime":1,"total_time":1,"estimated_time_to_regain_access":0}]}' + Access-Control-Allow-Origin: + - "*" + Facebook-Api-Version: + - v20.0 + Strict-Transport-Security: + - max-age=15552000; preload + Pragma: + - no-cache + Cache-Control: + - private, no-cache, no-store, must-revalidate + Expires: + - Sat, 01 Jan 2000 00:00:00 GMT + X-Fb-Request-Id: + - A-l5uxzCbPjsOvkwjPWUuw1 + X-Fb-Trace-Id: + - H9iBWKx8Ubk + X-Fb-Rev: + - '1017354952' + X-Fb-Debug: + - q//NlAA3wFkAgZadAKxj5IjY7lLfj43Sj5JOjLUWIlJ51h1dw3XT2jMvB1M9Y10TPaS+ZgAa5mYIs1w5zMPejg== + Date: + - Tue, 15 Oct 2024 21:31:03 GMT + X-Fb-Connection-Quality: + - EXCELLENT; q=0.9, rtt=27, rtx=0, c=10, mss=1380, tbw=3364, tp=-1, tpl=-1, + uplat=374, ullat=0 + Alt-Svc: + - h3=":443"; ma=86400 + Connection: + - keep-alive + Content-Length: + - '339' + body: + encoding: ASCII-8BIT + string: '{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=396305933546205&ext=1729028163&hash=ATv4PO2ix3ThulGe8hhrFCZTXSypEC6t51V6UBDSvRdwRw","mime_type":"application\/pdf","sha256":"9c0ec015ef2ec004c152dfa72cd07929f9fcb775d8d38aa1e9bf216ecfc003d7","file_size":14254,"id":"396305933546205","messaging_product":"whatsapp"}' + recorded_at: Tue, 15 Oct 2024 21:31:03 GMT +recorded_with: VCR 6.3.1 diff --git a/test/fixtures/vcr_cassettes/medias/test_get_video_with_success_response.yml b/test/fixtures/vcr_cassettes/medias/test_get_video_with_success_response.yml new file mode 100644 index 0000000..e4340c2 --- /dev/null +++ b/test/fixtures/vcr_cassettes/medias/test_get_video_with_success_response.yml @@ -0,0 +1,67 @@ +--- +http_interactions: +- request: + method: get + uri: https://graph.facebook.com/396305933546205 + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - Bearer + User-Agent: + - Faraday v2.12.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Etag: + - '"bbec17ddec5b682fcca3851ee86c35499d0ed4b6"' + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Accept-Encoding + - Origin + X-Business-Use-Case-Usage: + - '{"114503234599312":[{"type":"whatsapp","call_count":1,"total_cputime":1,"total_time":1,"estimated_time_to_regain_access":0}]}' + Access-Control-Allow-Origin: + - "*" + Facebook-Api-Version: + - v20.0 + Strict-Transport-Security: + - max-age=15552000; preload + Pragma: + - no-cache + Cache-Control: + - private, no-cache, no-store, must-revalidate + Expires: + - Sat, 01 Jan 2000 00:00:00 GMT + X-Fb-Request-Id: + - AMuadrms4PJsBAy9MFmdogI + X-Fb-Trace-Id: + - Hjx9cy7rcO9 + X-Fb-Rev: + - '1017369016' + X-Fb-Debug: + - zHWdA9GbpImpzOP9wLycbKpDNXhz6wi/+V/5pmO/ASIMxO3gd2w9P5q9hzLM+ygZcfbWmb60Ouz0kh2RDaR6WA== + Date: + - Tue, 15 Oct 2024 22:51:03 GMT + X-Fb-Connection-Quality: + - EXCELLENT; q=0.9, rtt=27, rtx=0, c=10, mss=1380, tbw=3365, tp=-1, tpl=-1, + uplat=552, ullat=0 + Alt-Svc: + - h3=":443"; ma=86400 + Connection: + - keep-alive + Content-Length: + - '339' + body: + encoding: ASCII-8BIT + string: '{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=396305933546205&ext=1729032963&hash=ATvggxtHdMmiuPe54wrgdkwLlJlD_Ya2ByuYUU9_PYE7ZA","mime_type":"application\/pdf","sha256":"9c0ec015ef2ec004c152dfa72cd07929f9fcb775d8d38aa1e9bf216ecfc003d7","file_size":14254,"id":"396305933546205","messaging_product":"whatsapp"}' + recorded_at: Tue, 15 Oct 2024 22:51:03 GMT +recorded_with: VCR 6.3.1 diff --git a/test/test_helper.rb b/test/test_helper.rb index 60f2eec..1507767 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,7 +10,6 @@ require "pry-nav" require "webmock/minitest" require "errors_helper" -require "api_response_helper" require 'vcr' VCR.configure do |config| diff --git a/test/whatsapp/api/business_profile_test.rb b/test/whatsapp/api/business_profile_test.rb index b0b8789..3100c48 100644 --- a/test/whatsapp/api/business_profile_test.rb +++ b/test/whatsapp/api/business_profile_test.rb @@ -3,12 +3,11 @@ require 'api/business_profile' require 'api/client' -require 'api_response_helper' + module WhatsappSdk module Api class BusinessProfileTest < Minitest::Test include(ErrorsHelper) - include(ApiResponseHelper) def setup client = Client.new(ENV.fetch('WHATSAPP_ACCESS_TOKEN', nil)) @@ -17,55 +16,30 @@ def setup def test_get_handles_error_response VCR.use_cassette('business_profile/details_handles_error_response') do - response = @business_profile_api.get(123_123) - assert_unsupported_request_error("get", response, "123123", "AYMXgC3SR8dC_HM7lrwoPOZ") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @business_profile_api.get(123_123) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123123", "AYMXgC3SR8dC_HM7lrwoPOZ", http_error.error_info) end end def test_get_accepts_fields fields = %w[vertical] VCR.use_cassette('business_profile/details_accepts_fields') do - response = @business_profile_api.get(107_878_721_936_019, fields: fields) + business_profile = @business_profile_api.get(107_878_721_936_019, fields: fields) - assert_ok_response(response) - assert_equal(%w[vertical messaging_product], response.raw_response["data"][0].keys) + assert_equal("UNDEFINED", business_profile.vertical) + assert_nil(business_profile.address) end end - def test_get_sends_all_fields_by_default - VCR.use_cassette('business_profile/details_sends_all_fields_by_default') do - response = @business_profile_api.get(107_878_721_936_019) - - assert_business_details_response( - { - about: nil, - messaging_product: "whatsapp", - address: nil, - description: nil, - email: nil, - profile_picture_url: nil, - websites: nil, - vertical: "UNDEFINED" - }, response - ) - end - end + def test_get_queries_all_fields_by_default + VCR.use_cassette('business_profile/details_queries_all_fields_by_default') do + business_profile = @business_profile_api.get(107_878_721_936_019) - def test_get_with_success_response - VCR.use_cassette('business_profile/details_with_success_response') do - response = @business_profile_api.get(107_878_721_936_019) - - assert_business_details_response( - { - messaging_product: "whatsapp", - address: "123, Main Street, New York, NY, 10001", - description: "This is a description", - email: "testing@gmail.com", - profile_picture_url: nil, - websites: ["https://www.google.com/"], - vertical: "EDU" - }, response - ) + assert_equal(business_profile_saved, business_profile) end end @@ -78,11 +52,12 @@ def test_update_returns_an_error_if_vertical_is_invalid def test_update_handles_error_response VCR.use_cassette('business_profile/update_handles_error_response') do - response = @business_profile_api.update( - phone_number_id: 123_123, params: { about: "Hey there! I am using WhatsApp." } - ) + http_error = assert_raises(Api::Responses::HttpResponseError) do + @business_profile_api.update(phone_number_id: 123_123, params: { about: "Hey there! I am using WhatsApp." }) + end - assert_unsupported_request_error("post", response, "123123", "Aqsz-RxXL8YZKhl3wBpoStg") + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("post", "123123", "Aqsz-RxXL8YZKhl3wBpoStg", http_error.error_info) end end @@ -96,32 +71,27 @@ def test_update_with_success_response websites: ["https://www.google.com"], vertical: "EDU" } - response = @business_profile_api.update(phone_number_id: 107_878_721_936_019, params: params) - - assert_ok_success_response(response) + assert(@business_profile_api.update(phone_number_id: 107_878_721_936_019, params: params)) end end private - def assert_business_details_response(expected_business_profile, response) - assert_ok_response(response) - - [ - [expected_business_profile[:about], response.data.about], - [expected_business_profile[:messaging_product], response.data.messaging_product], - [expected_business_profile[:address], response.data.address], - [expected_business_profile[:description], response.data.description], - [expected_business_profile[:email], response.data.email], - [expected_business_profile[:websites], response.data.websites], - [expected_business_profile[:vertical], response.data.vertical] - ].each do |expected_value, actual_value| - if expected_value.nil? - assert_nil(actual_value) - else - assert_equal(expected_value, actual_value) - end - end + def business_profile_saved + Resource::BusinessProfile.from_hash(business_profile_saved_hash) + end + + def business_profile_saved_hash + { + "about" => "A very cool business", + "messaging_product" => "whatsapp", + "address" => "123, Main Street, New York, NY, 10001", + "description" => "This is a description", + "email" => "testing@gmail.com", + "profile_picture_url" => nil, + "websites" => ["https://www.google.com/"], + "vertical" => "EDU" + } end end end diff --git a/test/whatsapp/api/medias_test.rb b/test/whatsapp/api/medias_test.rb index bd3b387..66779f9 100644 --- a/test/whatsapp/api/medias_test.rb +++ b/test/whatsapp/api/medias_test.rb @@ -9,7 +9,6 @@ module WhatsappSdk module Api class MediasTest < Minitest::Test include(ErrorsHelper) - include(ApiResponseHelper) def setup client = Client.new(ENV.fetch("WHATSAPP_ACCESS_TOKEN", nil)) @@ -19,46 +18,74 @@ def setup def test_get_handles_error_response VCR.use_cassette("medias/media_handles_error_response") do - response = @medias_api.get(media_id: "123_123") - assert_unsupported_request_error("get", response, "123_123", "ATf5-CLoxGyJeSu2vrRDOZR") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @medias_api.get(media_id: "123_123") + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123_123", "ATf5-CLoxGyJeSu2vrRDOZR", http_error.error_info) end end - def test_get_with_success_response + def test_get_image_with_success_response VCR.use_cassette("medias/media_with_success_response") do - response = @medias_api.get(media_id: "1761991787669262") - - assert_media_response({ - url: "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1761991787669262&ext=1728904986&hash=ATta-PkMyBz0aTF9b0CVDimLtAkAgpdXQa6t5x1KgUOu-Q", - mime_type: "image/png", - sha256: "c86c28d437534f7367e73b283155c083dddfdaf7f9b4dfae27e140f880035141", - file_size: 182_859, - id: "1761991787669262", - messaging_product: "whatsapp" - }, response) + media = @medias_api.get(media_id: "1761991787669262") + + assert_media_response( + { + url: "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1761991787669262&ext=1728904986&hash=ATta-PkMyBz0aTF9b0CVDimLtAkAgpdXQa6t5x1KgUOu-Q", + mime_type: "image/png", + sha256: "c86c28d437534f7367e73b283155c083dddfdaf7f9b4dfae27e140f880035141", + file_size: 182_859, + id: "1761991787669262", + messaging_product: "whatsapp" + }, + media + ) + end + end + + def test_get_video_with_success_response + VCR.use_cassette("medias/test_get_video_with_success_response") do + media = @medias_api.get(media_id: "396305933546205") + + assert_media_response( + { + url: "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=396305933546205&ext=1729032963&hash=ATvggxtHdMmiuPe54wrgdkwLlJlD_Ya2ByuYUU9_PYE7ZA", + mime_type: "application/pdf", + sha256: "9c0ec015ef2ec004c152dfa72cd07929f9fcb775d8d38aa1e9bf216ecfc003d7", + file_size: 14_254, + id: "396305933546205", + messaging_product: "whatsapp" + }, + media + ) end end def test_delete_media_handles_error_response VCR.use_cassette("medias/delete_media_handles_error_response") do - response = @medias_api.delete(media_id: "123_123") - assert_error_response( + http_error = assert_raises(Api::Responses::HttpResponseError) do + @medias_api.delete(media_id: "123_123") + end + + assert_equal(400, http_error.http_status) + assert_error_info( { - message: "(#100) Invalid post_id parameter", - type: "OAuthException", code: 100, + error_subcode: nil, + type: "OAuthException", + message: "(#100) Invalid post_id parameter", fbtrace_id: "AIejXf67At-GZg4mX_Cs-Md" }, - response + http_error.error_info ) end end def test_delete_media_with_success_response VCR.use_cassette("medias/delete_media_with_success_response") do - response = @medias_api.delete(media_id: "1953032278471180") - - assert_ok_success_response(response) + assert(@medias_api.delete(media_id: "1953032278471180")) end end @@ -68,8 +95,7 @@ def test_delete_media_sends_valid_params endpoint: "/1" ).returns({ "success" => true }) - response = @medias_api.delete(media_id: "1") - assert_ok_success_response(response) + assert(@medias_api.delete(media_id: "1")) end def test_upload_media_raises_an_error_if_the_file_passed_does_not_exists @@ -83,10 +109,13 @@ def test_upload_media_raises_an_error_if_the_file_passed_does_not_exists def test_upload_media_handles_error_response VCR.use_cassette("medias/upload_media_handles_error_response") do - response = @medias_api.upload(sender_id: "1234567", file_path: "test/fixtures/assets/whatsapp.png", - type: "image/png") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @medias_api.upload(sender_id: "1234567", file_path: "test/fixtures/assets/whatsapp.png", + type: "image/png") + end - assert_unsupported_request_error("post", response, "1234567", "AntlLyAlE6ZvA8AWFzcRYzZ") + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("post", "1234567", "AntlLyAlE6ZvA8AWFzcRYzZ", http_error.error_info) end end @@ -95,9 +124,8 @@ def test_upload_image_media_with_success_response response = @medias_api.upload(sender_id: @sender_id, file_path: "test/fixtures/assets/whatsapp.png", type: "image/png") - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal("3281363908663165", response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal("3281363908663165", response.id) end end @@ -106,9 +134,8 @@ def test_upload_audio_media_with_success_response response = @medias_api.upload(sender_id: @sender_id, file_path: "test/fixtures/assets/downloaded_audio.ogg", type: "audio/ogg") - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal("914268667232441", response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal("914268667232441", response.id) end end @@ -117,9 +144,8 @@ def test_upload_video_media_with_success_response response = @medias_api.upload(sender_id: @sender_id, file_path: "test/fixtures/assets/riquelme.mp4", type: "video/mp4") - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal("1236350287490060", response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal("1236350287490060", response.id) end end @@ -128,9 +154,8 @@ def test_upload_document_media_with_success_response response = @medias_api.upload(sender_id: @sender_id, file_path: "test/fixtures/assets/document.pdf", type: "application/pdf") - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal("347507378388855", response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal("347507378388855", response.id) end end @@ -139,9 +164,8 @@ def test_upload_sticker_media_with_success_response response = @medias_api.upload(sender_id: @sender_id, file_path: "test/fixtures/assets/sticker.webp", type: "image/webp") - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal("503335829198997", response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal("503335829198997", response.id) end end @@ -173,19 +197,19 @@ def test_upload_media_sends_valid_params response = @medias_api.upload(sender_id: 123, file_path: file_path, type: type, headers: custom_headers) - assert_ok_response(response) - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal(media_id, response.data.id) + assert_equal(Api::Responses::IdResponse, response.class) + assert_equal(media_id, response.id) end def test_download_media_handles_error_response VCR.use_cassette("medias/download_media_handles_error_response") do - response = @medias_api.download(url: url_example, media_type: "image/png", - file_path: "test/fixtures/assets/testing.png") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @medias_api.download(url: url_example, media_type: "image/png", + file_path: "test/fixtures/assets/testing.png") + end - assert_predicate(response, :error?) - assert_equal(Responses::ErrorResponse, response.error.class) - assert_equal("301", response.error.status) + assert_equal("301", http_error.http_status) + assert_equal("Redirecting to https://ignaciochiazzo.com/", http_error.body["message"]) end end @@ -195,26 +219,35 @@ def test_download_media_sends_valid_params .with(url: url_example, content_type_header: "image/png", file_path: file_path) .returns(Net::HTTPOK.new(true, 200, "OK")) - response = @medias_api.download(url: url_example, file_path: file_path, media_type: "image/png") - assert_ok_success_response(response) + assert( + @medias_api.download(url: url_example, file_path: file_path, media_type: "image/png") + ) end def test_download_allows_unsupported_media_type unsupported_media_type = "application/x-zip-compressed" file_path = "test/fixtures/assets/testing.zip" - @medias_api.expects(:download_file).with(url: url_example, content_type_header: unsupported_media_type, - file_path: file_path) - .returns(Net::HTTPOK.new(true, 200, "OK")) - response = @medias_api.download(url: url_example, file_path: file_path, media_type: unsupported_media_type) - assert_ok_success_response(response) + + mock = Net::HTTPOK.new(true, 200, "OK") + mock.instance_variable_set(:@body, { "success" => true }.to_json) + + @medias_api.expects(:download_file) + .with(url: url_example, content_type_header: unsupported_media_type, file_path: file_path) + .returns(mock) + + assert( + @medias_api.download(url: url_example, file_path: file_path, media_type: unsupported_media_type) + ) end def test_download_media_success_response VCR.use_cassette("medias/download_media_success_response") do url = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1761991787669262&ext=1728905510&hash=ATsz9FvlFt63X6Vj00u7PY7SNVCDtCYDeyUqClaX8b5rAg" - response = @medias_api.download(url: url, file_path: "test/fixtures/assets/testing.png", - media_type: "image/png") - assert_ok_success_response(response) + assert( + @medias_api.download( + url: url, file_path: "test/fixtures/assets/testing.png", media_type: "image/png" + ) + ) end end @@ -224,16 +257,14 @@ def url_example "https://www.ignaciochiazzo.com" end - def assert_media_response(expected_media_response, response) - assert_ok_response(response) - - assert_equal(Responses::MediaDataResponse, response.data.class) - assert_equal(expected_media_response[:id], response.data.id) - assert_equal(expected_media_response[:url], response.data.url) - assert_equal(expected_media_response[:mime_type], response.data.mime_type) - assert_equal(expected_media_response[:sha256], response.data.sha256) - assert_equal(expected_media_response[:file_size], response.data.file_size) - assert_equal(expected_media_response[:messaging_product], response.data.messaging_product) + def assert_media_response(expected_media_response, media) + assert_equal(WhatsappSdk::Resource::Media, media.class) + assert_equal(expected_media_response[:id], media.id) + assert_equal(expected_media_response[:url], media.url) + assert_equal(expected_media_response[:mime_type], media.mime_type) + assert_equal(expected_media_response[:sha256], media.sha256) + assert_equal(expected_media_response[:file_size], media.file_size) + assert_equal(expected_media_response[:messaging_product], media.messaging_product) end end end diff --git a/test/whatsapp/api/messages_test.rb b/test/whatsapp/api/messages_test.rb index adf2367..45ea9fd 100644 --- a/test/whatsapp/api/messages_test.rb +++ b/test/whatsapp/api/messages_test.rb @@ -2,7 +2,6 @@ # frozen_string_literal: true require "test_helper" -require 'api/responses/read_message_data_response' require 'api/messages' require 'resource/address_type' require 'resource/address' @@ -28,7 +27,6 @@ module Api class MessagesTest < Minitest::Test include(ContactHelper) include(ErrorsHelper) - include(ApiResponseHelper) def setup client = Client.new(ENV.fetch("WHATSAPP_ACCESS_TOKEN", nil)) @@ -39,11 +37,11 @@ def setup def test_send_text_handles_error_response VCR.use_cassette("messages/send_text_handles_error_response") do - response = @messages_api.send_text( - sender_id: 123_123, recipient_number: 56_789, message: "hola" - ) + http_error = assert_raises(Api::Responses::HttpResponseError) do + @messages_api.send_text(sender_id: 123_123, recipient_number: 56_789, message: "hola") + end - assert_unsupported_request_error("post", response, "123123", "AzJYAMZdf4WaDlLDXeqjTr9") + assert_unsupported_request_error_v2("post", "123123", "AzJYAMZdf4WaDlLDXeqjTr9", http_error.error_info) end end @@ -118,7 +116,10 @@ def test_send_location_message_sends_the_correct_params longitude: longitude, latitude: latitude, name: name, address: address ) - assert_ok_response(message_response) + assert_message_response({ + contacts: [{ "input" => "1234", "wa_id" => "1234" }], + messages: [{ "id" => "9876" }] + }, message_response) end def test_send_image_raises_an_error_if_link_and_image_are_not_provided @@ -384,16 +385,20 @@ def test_read_message_with_a_valid_response def test_read_message_with_an_invalid_response VCR.use_cassette("messages/read_message_with_an_invalid_response") do - response = @messages_api.read_message(sender_id: @sender_id, message_id: "12345") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @messages_api.read_message(sender_id: @sender_id, message_id: "12345") + end - assert_error_response( + assert_equal(400, http_error.http_status) + assert_error_info( { - message: "(#100) Invalid parameter", - type: "OAuthException", code: 100, + error_subcode: nil, + type: "OAuthException", + message: "(#100) Invalid parameter", fbtrace_id: "A3PJ2-1aODhbAGAh66zQkwf" }, - response + http_error.error_info ) end end @@ -459,7 +464,7 @@ def test_send_template_with_success_response_by_passing_components_json def test_send_template_with_success_response_by_passing_components_sends_the_correct_params currency = Resource::Currency.new(code: "USD", amount: 1000, fallback_value: "1000") date_time = Resource::DateTime.new(fallback_value: "2020-01-01T00:00:00Z") - image = Resource::Media.new(type: Resource::Media::Type::IMAGE, link: "http(s)://URL") + image = Resource::MediaComponent.new(type: Resource::MediaComponent::Type::IMAGE, link: "http(s)://URL") location = Resource::Location.new(latitude: 25.779510, longitude: -80.338631, name: "miami store", address: "820 nw 87th ave, miami, fl") @@ -611,7 +616,10 @@ def test_send_template_with_success_response_by_passing_components_sends_the_cor components: [header_component, body_component, button_component1, button_component2, location_component] ) - assert_predicate(message_response, :ok?) + assert_message_response({ + contacts: [{ "input" => "1234", "wa_id" => "1234" }], + messages: [{ "id" => "9876" }] + }, message_response) end def test_send_interactive_reply_buttons_with_success_response_by_passing_interactive_json @@ -722,13 +730,11 @@ def valid_response end def assert_message_response(expected_message, response) - assert_ok_response(response) - assert_instance_of(Response, response) - assert_instance_of(Responses::MessageDataResponse, response.data) + assert_instance_of(Api::Responses::MessageDataResponse, response) - assert_equal(1, response.data.contacts.size) - assert_contacts(expected_message[:contacts], response.data.contacts) - assert_messages(expected_message[:messages], response.data.messages) + assert_equal(1, response.contacts.size) + assert_contacts(expected_message[:contacts], response.contacts) + assert_messages(expected_message[:messages], response.messages) end def assert_messages(expected_messages, messages) diff --git a/test/whatsapp/api/phone_numbers_test.rb b/test/whatsapp/api/phone_numbers_test.rb index 28a9b65..5b42d13 100644 --- a/test/whatsapp/api/phone_numbers_test.rb +++ b/test/whatsapp/api/phone_numbers_test.rb @@ -9,7 +9,6 @@ module WhatsappSdk module Api class PhoneNumbersTest < Minitest::Test include(ErrorsHelper) - include(ApiResponseHelper) def setup client = Client.new(ENV.fetch('WHATSAPP_ACCESS_TOKEN', nil)) @@ -18,17 +17,25 @@ def setup def test_list_handles_error_response VCR.use_cassette('phone_numbers/registered_numbers_handles_error_response') do - response = @phone_numbers_api.list(123_123) - assert_unsupported_request_error("get", response, "123123", "AFZgW89DkR0hLRFJP40NTd6") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @phone_numbers_api.list(123_123) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123123", "AFZgW89DkR0hLRFJP40NTd6", http_error.error_info) end end def test_list_with_success_response VCR.use_cassette('phone_numbers/registered_numbers_with_success_response') do - response = @phone_numbers_api.list(114_503_234_599_312) + phone_numbers_pagination = @phone_numbers_api.list(114_503_234_599_312) + + assert_equal(1, phone_numbers_pagination.records.size) + assert(phone_numbers_pagination.before) + assert(phone_numbers_pagination.after) - expected_phone_numbers = [registered_phone_number] - assert_phone_numbers_success_response(expected_phone_numbers, response) + phone_number = phone_numbers_pagination.records.first + assert_equal(registered_phone_number, phone_number) end end @@ -38,25 +45,32 @@ def test_list_sends_valid_params endpoint: "123123/phone_numbers?fields=#{PhoneNumbers::DEFAULT_FIELDS}" ).returns( { - "data" => [registered_phone_number], + "data" => [registered_phone_number_hash], "paging" => { "cursors" => { "before" => "1", "after" => "2" } } } ) - @phone_numbers_api.list(123_123) + phone_numbers_pagination = @phone_numbers_api.list(123_123) + assert_equal(1, phone_numbers_pagination.records.size) + phone_number = phone_numbers_pagination.records.first + assert_equal(registered_phone_number, phone_number) end def test_get_handles_error_response VCR.use_cassette('phone_numbers/registered_number_handles_error_response') do - response = @phone_numbers_api.get(123_123) - assert_unsupported_request_error("get", response, "123123", "AlicHjOpoShf8TV_iXRm1pW") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @phone_numbers_api.get(123_123) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123123", "AlicHjOpoShf8TV_iXRm1pW", http_error.error_info) end end def test_get_with_success_response VCR.use_cassette('phone_numbers/registered_number_with_success_response') do - response = @phone_numbers_api.get(107_878_721_936_019) - assert_phone_number_success_response(registered_phone_number, response) + phone_number = @phone_numbers_api.get(107_878_721_936_019) + assert_equal(registered_phone_number, phone_number) end end @@ -68,20 +82,23 @@ def test_get_sends_valid_params { "data" => [registered_phone_number] } ) - @phone_numbers_api.get(123_123) + assert(@phone_numbers_api.get(123_123)) end def test_register_number_handles_error_response VCR.use_cassette('phone_numbers/register_number_handles_error_response') do - response = @phone_numbers_api.register_number(123_123, 123) - assert_unsupported_request_error("post", response, "123123", "AsINUN3wXWCUKt1M4Uyn7Pc") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @phone_numbers_api.register_number(123_123, 123) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("post", "123123", "AsINUN3wXWCUKt1M4Uyn7Pc", http_error.error_info) end end def test_register_number_with_success_response VCR.use_cassette('phone_numbers/register_number_with_success_response') do - response = @phone_numbers_api.register_number(107_878_721_936_019, 123_456) - assert_ok_response(response) + assert(@phone_numbers_api.register_number(107_878_721_936_019, 123_456)) end end @@ -94,21 +111,23 @@ def test_register_number_sends_valid_params } ).returns({ "success" => true }) - response = @phone_numbers_api.register_number(123_123, 123_456) - assert_ok_response(response) + assert(@phone_numbers_api.register_number(123_123, 123_456)) end def test_deregister_number_handles_error_response VCR.use_cassette('phone_numbers/deregister_number_handles_error_response') do - response = @phone_numbers_api.deregister_number(123_123) - assert_unsupported_request_error("post", response, "123123", "AFeF4zcpff3iqz4VbpBO2Yj") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @phone_numbers_api.deregister_number(123_123) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("post", "123123", "AFeF4zcpff3iqz4VbpBO2Yj", http_error.error_info) end end def test_deregister_number_with_success_response VCR.use_cassette('phone_numbers/deregister_number_with_success_response') do - response = @phone_numbers_api.deregister_number(107_878_721_936_019) - assert_ok_response(response) + assert(@phone_numbers_api.deregister_number(107_878_721_936_019)) end end @@ -119,13 +138,12 @@ def test_deregister_number_sends_valid_params params: {} ).returns({ "success" => true }) - response = @phone_numbers_api.deregister_number(123_123) - assert_ok_response(response) + assert(@phone_numbers_api.deregister_number(123_123)) end private - def registered_phone_number + def registered_phone_number_hash { "verified_name" => "Test Number", "code_verification_status" => "NOT_VERIFIED", @@ -144,48 +162,8 @@ def registered_phone_number } end - def assert_phone_numbers_success_response(expected_phone_numbers, response) - assert_ok_response(response) - assert_equal(expected_phone_numbers.size, response.data.phone_numbers.size) - expected_phone_numbers.each do |expected_phone_number| - assert_phone_number(expected_phone_number, response.data.phone_numbers.first) - end - end - - def assert_phone_number_success_response(expected_phone_number, response) - assert_ok_response(response) - assert_phone_number(expected_phone_number, response.data) - end - - def assert_phone_number_mock_response(expected_phone_number, response) - assert_ok_response(response) - assert_phone_number(expected_phone_number, response.data) - end - - def assert_phone_number(expected_phone_number, phone_number) - [ - [expected_phone_number["id"], phone_number.id], - [expected_phone_number["display_phone_number"], phone_number.display_phone_number], - [expected_phone_number["quality_rating"], phone_number.quality_rating], - [expected_phone_number["verified_name"], phone_number.verified_name], - [expected_phone_number["code_verification_status"], phone_number.code_verification_status], - [expected_phone_number["is_official_business_account"], phone_number.is_official_business_account], - [expected_phone_number["account_mode"], phone_number.account_mode], - [expected_phone_number["eligibility_for_api_business_global_search"], - phone_number.eligibility_for_api_business_global_search], - [expected_phone_number["is_pin_enabled"], phone_number.is_pin_enabled], - [expected_phone_number["name_status"], phone_number.name_status], - [expected_phone_number["new_name_status"], phone_number.new_name_status], - [expected_phone_number["status"], phone_number.status], - [expected_phone_number["search_visibility"], phone_number.search_visibility], - [expected_phone_number["certificate"], phone_number.certificate] - ].each do |expected, actual| - if expected.nil? - assert_nil(actual) - else - assert_equal(expected, actual) - end - end + def registered_phone_number + Resource::PhoneNumber.from_hash(registered_phone_number_hash) end end end diff --git a/test/whatsapp/api/templates_test.rb b/test/whatsapp/api/templates_test.rb index f3a5a02..b17bc16 100644 --- a/test/whatsapp/api/templates_test.rb +++ b/test/whatsapp/api/templates_test.rb @@ -5,15 +5,11 @@ require 'api/templates' require 'api/client' require 'api/messages' -require 'api/responses/template_data_response' -require 'resource/errors' -require 'api_response_helper' module WhatsappSdk module Api class TemplatesTest < Minitest::Test include(ErrorsHelper) - include(ApiResponseHelper) def setup client = Client.new(ENV.fetch('WHATSAPP_ACCESS_TOKEN', nil)) @@ -56,49 +52,72 @@ def test_create_a_template_with_valid_params_and_components components_json: basic_components_json, allow_category_change: true ) - assert_ok_response(new_template) - assert_equal(Responses::TemplateDataResponse, new_template.data.class) - assert_equal("MARKETING", new_template.data.template.category) - assert_equal("PENDING", new_template.data.template.status) - assert(new_template.data.template.id) + assert_equal(Resource::Template, new_template.class) + assert_equal("MARKETING", new_template.category) + assert_equal("PENDING", new_template.status) + assert(new_template.id) end end ##### GET Templates def test_get_templates VCR.use_cassette('templates/get_templates') do - templates_response = @templates_api.list(business_id: 114_503_234_599_312) - assert_ok_response(templates_response) - assert_equal(Responses::TemplatesDataResponse, templates_response.data.class) - assert_equal(25, templates_response.data.templates.size) - assert_equal(Responses::TemplateDataResponse, templates_response.data.templates.first.class) + templates_pagination = @templates_api.list(business_id: 114_503_234_599_312) + + assert_equal(25, templates_pagination.records.size) + assert(templates_pagination.before) + assert(templates_pagination.after) + + first_template = templates_pagination.records.first + assert_equal(Resource::Template, first_template.class) + assert_equal("711633970112948", first_template.id) + assert_equal("hello_world", first_template.name) + assert_equal("MARKETING", first_template.category) + assert_equal("en_US", first_template.language) + assert_equal("APPROVED", first_template.status) + assert_equal( + [{ "type" => "HEADER", "format" => "TEXT", "text" => "Hello World" }, + { "type" => "BODY", + "text" => + "Welcome and congratulations!! This message demonstrates your ability to send a message " \ + "notification from WhatsApp Business Platform’s Cloud API. Thank you for taking the time "\ + "to test with us." }, + { "type" => "FOOTER", "text" => "WhatsApp Business API Team" }], + first_template.components_json + ) end end def test_get_templates_when_an_error_is_returned VCR.use_cassette('templates/get_templates_when_an_error_is_returned') do - templates_response = @templates_api.list(business_id: 123_456) - assert_unsupported_request_error("get", templates_response, "123456", "AhvemSTIjTs-WJikZKj0mLu") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @templates_api.list(business_id: 123_456) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123456", "AhvemSTIjTs-WJikZKj0mLu", http_error.error_info) end end ##### GET Message Template namespace def test_get_template_namespace VCR.use_cassette('templates/get_template_namespace') do - templates_response = @templates_api.get_message_template_namespace(business_id: 114_503_234_599_312) - - assert_ok_response(templates_response) - assert_equal(Responses::MessageTemplateNamespaceDataResponse, templates_response.data.class) - assert_equal("114503234599312", templates_response.data.id) - assert_equal("ffac72fb_6591_4cb3_bb24_0c33a6633999", templates_response.data.message_template_namespace) + message_template_namespace = @templates_api.get_message_template_namespace(business_id: 114_503_234_599_312) + assert_equal(Resource::MessageTemplateNamespace, message_template_namespace.class) + assert_equal("114503234599312", message_template_namespace.id) + assert_equal("ffac72fb_6591_4cb3_bb24_0c33a6633999", message_template_namespace.message_template_namespace) end end def test_get_template_namespace_when_an_error_is_returned VCR.use_cassette('templates/get_template_namespace_when_an_error_is_returned') do fake_business_id = 123_456 - templates_response = @templates_api.get_message_template_namespace(business_id: fake_business_id) - assert_unsupported_request_error("get", templates_response, "123456", "A8nD588C9Qpiid9YWpnwzYB") + http_error = assert_raises(Api::Responses::HttpResponseError) do + @templates_api.get_message_template_namespace(business_id: fake_business_id) + end + + assert_equal(400, http_error.http_status) + assert_unsupported_request_error_v2("get", "123456", "A8nD588C9Qpiid9YWpnwzYB", http_error.error_info) end end @@ -113,41 +132,50 @@ def test_update_a_template_raises_an_error_when_category_is_invalid def test_update_a_template_with_components_and_category VCR.use_cassette('templates/update_a_template_with_components_and_category') do - template_response = @templates_api.update( - template_id: "1713674996085293", components_json: basic_components_json + assert( + @templates_api.update( + template_id: "1713674996085293", components_json: basic_components_json + ) ) - - assert_ok_success_response(template_response) end end ##### Delete Message Template def test_delete_template_by_name VCR.use_cassette('templates/delete_template_by_name') do - response = @templates_api.delete(business_id: 114_503_234_599_312, name: "seasonal_promotion") - assert_ok_success_response(response) + assert( + @templates_api.delete( + business_id: 114_503_234_599_312, name: "seasonal_promotion" + ) + ) end end def test_delete_template_by_id VCR.use_cassette('templates/delete_template_by_id') do - response = @templates_api.delete(business_id: 114_503_234_599_312, name: "seasonal_promotion", - hsm_id: 1_075_472_707_447_727) - assert_ok_success_response(response) + assert( + @templates_api.delete( + business_id: 114_503_234_599_312, name: "seasonal_promotion", hsm_id: 1_075_472_707_447_727 + ) + ) end end def test_delete_template_with_error_response VCR.use_cassette('templates/delete_template_with_error_response') do - response = @templates_api.delete(business_id: 114_503_234_599_312, name: "fake name") - assert_error_response( + http_error = assert_raises(Api::Responses::HttpResponseError) do + @templates_api.delete(business_id: 114_503_234_599_312, name: "fake name") + end + + assert_error_info( { code: 100, error_subcode: 2_593_002, + type: "OAuthException", message: "Invalid parameter", fbtrace_id: "Ax5tIkALJCP4l5S8jbPvW2n" }, - response + http_error.error_info ) end end diff --git a/test/whatsapp/contact_helper.rb b/test/whatsapp/contact_helper.rb index a59aa93..996bbc1 100644 --- a/test/whatsapp/contact_helper.rb +++ b/test/whatsapp/contact_helper.rb @@ -49,8 +49,8 @@ def create_org end def create_phone_numbers - phone1 = Resource::PhoneNumber.new(phone: "1234567", type: Resource::AddressType::HOME, wa_id: "1234") - phone2 = Resource::PhoneNumber.new(phone: "9876543", type: Resource::AddressType::WORK, wa_id: "1234") + phone1 = Resource::PhoneNumberComponent.new(phone: "1234567", type: Resource::AddressType::HOME, wa_id: "1234") + phone2 = Resource::PhoneNumberComponent.new(phone: "9876543", type: Resource::AddressType::WORK, wa_id: "1234") [phone1, phone2] end @@ -69,7 +69,7 @@ def create_contact emails: create_emails, name: create_name, org: create_org, - phones: create_phone_numbers, + phones: create_phone_numbers, # fix me urls: create_urls ) end diff --git a/test/whatsapp/resource/component_test.rb b/test/whatsapp/resource/component_test.rb index 789a9eb..a5bf89d 100644 --- a/test/whatsapp/resource/component_test.rb +++ b/test/whatsapp/resource/component_test.rb @@ -32,11 +32,11 @@ def test_validation end def test_add_parameters - image = Media.new(type: Media::Type::IMAGE, - link: "http(s)://URL", caption: "caption") - document = Media.new(type: Media::Type::DOCUMENT, - link: "http(s)://URL", filename: "txt.rb") - video = Media.new(type: Media::Type::VIDEO, id: "123") + image = MediaComponent.new(type: MediaComponent::Type::IMAGE, + link: "http(s)://URL", caption: "caption") + document = MediaComponent.new(type: MediaComponent::Type::DOCUMENT, + link: "http(s)://URL", filename: "txt.rb") + video = MediaComponent.new(type: MediaComponent::Type::VIDEO, id: "123") currency = Currency.new(code: "USD", amount: 1000, fallback_value: "1000") date_time = DateTime.new(fallback_value: "2020-01-01T00:00:00Z") @@ -67,7 +67,7 @@ def test_add_parameters end def test_to_json_header_component - image = Media.new(type: Media::Type::IMAGE, link: "http(s)://URL", caption: "caption") + image = MediaComponent.new(type: MediaComponent::Type::IMAGE, link: "http(s)://URL", caption: "caption") parameter_image = ParameterObject.new(type: ParameterObject::Type::IMAGE, image: image) header_component = Component.new( diff --git a/test/whatsapp/resource/media_test.rb b/test/whatsapp/resource/media_component_test.rb similarity index 55% rename from test/whatsapp/resource/media_test.rb rename to test/whatsapp/resource/media_component_test.rb index b10c92c..a5ad189 100644 --- a/test/whatsapp/resource/media_test.rb +++ b/test/whatsapp/resource/media_component_test.rb @@ -7,10 +7,10 @@ module WhatsappSdk module Resource module Resource - class MediaTest < Minitest::Test + class MediaComponentTest < Minitest::Test def test_raise_an_error_when_filename_is_passed_and_type_is_not_document - error = assert_raises(Media::InvalidMedia) do - Media.new(type: Media::Type::STICKER, filename: "afs") + error = assert_raises(MediaComponent::InvalidMedia) do + MediaComponent.new(type: MediaComponent::Type::STICKER, filename: "afs") end assert_equal(:filename, error.field) @@ -18,8 +18,8 @@ def test_raise_an_error_when_filename_is_passed_and_type_is_not_document end def test_raise_an_error_when_caption_is_passed_and_type_is_not_document_nor_image - error = assert_raises(Media::InvalidMedia) do - Media.new(type: Media::Type::VIDEO, caption: "I am a caption") + error = assert_raises(MediaComponent::InvalidMedia) do + MediaComponent.new(type: MediaComponent::Type::VIDEO, caption: "I am a caption") end assert_equal(:caption, error.field) @@ -27,11 +27,11 @@ def test_raise_an_error_when_caption_is_passed_and_type_is_not_document_nor_imag end def test_to_json - image = Media.new(type: Media::Type::IMAGE, link: "http(s)://URL", caption: "caption") - document = Media.new(type: Media::Type::DOCUMENT, link: "http(s)://URL", filename: "txt.rb") - video = Media.new(type: Media::Type::VIDEO, id: "123") - audio = Media.new(type: Media::Type::AUDIO, id: "456") - sticker = Media.new(type: Media::Type::STICKER, id: "789") + image = MediaComponent.new(type: MediaComponent::Type::IMAGE, link: "http(s)://URL", caption: "caption") + document = MediaComponent.new(type: MediaComponent::Type::DOCUMENT, link: "http(s)://URL", filename: "txt.rb") + video = MediaComponent.new(type: MediaComponent::Type::VIDEO, id: "123") + audio = MediaComponent.new(type: MediaComponent::Type::AUDIO, id: "456") + sticker = MediaComponent.new(type: MediaComponent::Type::STICKER, id: "789") assert_equal({ link: "http(s)://URL", caption: "caption" }, image.to_json) assert_equal({ link: "http(s)://URL", filename: "txt.rb" }, document.to_json) diff --git a/test/whatsapp/resource/parameter_object_test.rb b/test/whatsapp/resource/parameter_object_test.rb index ef0c67c..3012b85 100644 --- a/test/whatsapp/resource/parameter_object_test.rb +++ b/test/whatsapp/resource/parameter_object_test.rb @@ -15,11 +15,11 @@ module Resource module Resource class ParameterObjectTest < Minitest::Test def setup - @image_media = Media.new(type: Media::Type::IMAGE, - link: "http(s)://URL", caption: "caption") - @document_media = Media.new(type: Media::Type::DOCUMENT, - link: "http://URL", filename: "txt.rb") - @video_media = Media.new(type: Media::Type::VIDEO, id: "123") + @image_media = MediaComponent.new(type: MediaComponent::Type::IMAGE, + link: "http(s)://URL", caption: "caption") + @document_media = MediaComponent.new(type: MediaComponent::Type::DOCUMENT, + link: "http://URL", filename: "txt.rb") + @video_media = MediaComponent.new(type: MediaComponent::Type::VIDEO, id: "123") @currency = Currency.new(code: "USD", amount: 1000, fallback_value: "USD") @date_time = DateTime.new(fallback_value: "2020-01-01T00:00:00Z") @location = Location.new(latitude: 25.779510, longitude: -80.338631, name: "Miami Store",