fix: z-api read message (#165)

* fix: z-api read messages

* fix: use threads for parallel requests

* feat: use jobs instead

* fix: refactor ZapiReadMessageJob to use service method for sending read messages
This commit is contained in:
Gabriel Jablonski 2025-12-12 09:58:12 -03:00 committed by GitHub
parent 0f56c51e68
commit 774c168d94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 123 additions and 19 deletions

View File

@ -0,0 +1,8 @@
class Channels::Whatsapp::ZapiReadMessageJob < ApplicationJob
queue_as :default
def perform(whatsapp_channel, phone, message_source_id)
service = Whatsapp::Providers::WhatsappZapiService.new(whatsapp_channel: whatsapp_channel)
service.send_read_message(phone, message_source_id)
end
end

View File

@ -81,21 +81,28 @@ class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseServic
end
def read_messages(messages, recipient_id:, **)
# NOTE: Z-API will handle marking previous messages as read.
last_message = messages.last
phone = recipient_id.delete('+')
messages.each do |message|
next if message.source_id.blank?
Channels::Whatsapp::ZapiReadMessageJob.perform_later(whatsapp_channel, phone, message.source_id)
end
true
end
def send_read_message(phone, message_source_id)
response = HTTParty.post(
"#{api_instance_path_with_token}/read-message",
headers: api_headers,
body: {
phone: recipient_id.delete('+'),
messageId: last_message.source_id
phone: phone,
messageId: message_source_id
}.to_json
)
raise ProviderUnavailableError unless process_response(response)
true
process_response(response)
end
def on_whatsapp(phone_number)

View File

@ -0,0 +1,71 @@
require 'rails_helper'
RSpec.describe Channels::Whatsapp::ZapiReadMessageJob do
let(:whatsapp_channel) do
create(:channel_whatsapp,
provider: 'zapi',
sync_templates: false,
validate_provider_config: false,
provider_config: {
'instance_id' => 'test-instance',
'token' => 'test-token',
'client_token' => 'test-client-token'
})
end
let(:phone) { '551187654321' }
let(:message_source_id) { 'msg_123' }
let(:api_base_path) { Whatsapp::Providers::WhatsappZapiService::API_BASE_PATH }
let(:api_instance_path_with_token) { "#{api_base_path}/instances/test-instance/token/test-token" }
def stub_headers
{
'Accept' => '*/*',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Content-Type' => 'application/json',
'Client-Token' => 'test-client-token',
'User-Agent' => 'Ruby'
}
end
describe '#perform' do
context 'when response is successful' do
it 'sends a read message request to Z-API' do
stub_request(:post, "#{api_instance_path_with_token}/read-message")
.with(
headers: stub_headers,
body: {
phone: phone,
messageId: message_source_id
}.to_json
)
.to_return(status: 200)
described_class.perform_now(whatsapp_channel, phone, message_source_id)
expect(a_request(:post, "#{api_instance_path_with_token}/read-message")
.with(
headers: stub_headers,
body: {
phone: phone,
messageId: message_source_id
}.to_json
)).to have_been_made.once
end
end
context 'when response is unsuccessful' do
it 'logs the error' do
stub_request(:post, "#{api_instance_path_with_token}/read-message")
.with(headers: stub_headers)
.to_return(status: 400, body: 'error message')
allow(Rails.logger).to receive(:error)
described_class.perform_now(whatsapp_channel, phone, message_source_id)
expect(Rails.logger).to have_received(:error).with('error message')
end
end
end
end

View File

@ -194,37 +194,55 @@ describe Whatsapp::Providers::WhatsappZapiService do
end
describe '#read_messages' do
let(:messages) { [create(:message), message] }
let(:first_message) { create(:message, source_id: 'msg_first') }
let(:second_message) { create(:message, source_id: 'msg_second') }
let(:messages) { [first_message, second_message] }
it 'enqueues a job for each message' do
service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
expect(Channels::Whatsapp::ZapiReadMessageJob).to have_been_enqueued.with(whatsapp_channel, test_send_phone_number, first_message.source_id)
expect(Channels::Whatsapp::ZapiReadMessageJob).to have_been_enqueued.with(whatsapp_channel, test_send_phone_number, second_message.source_id)
end
it 'returns true' do
result = service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
expect(result).to be(true)
end
end
describe '#send_read_message' do
let(:phone) { test_send_phone_number }
let(:message_source_id) { 'msg_123' }
context 'when response is successful' do
it 'marks messages as read by referencing last message id' do
it 'sends a read message request to Z-API' do
stub_request(:post, "#{api_instance_path_with_token}/read-message")
.with(
headers: stub_headers,
body: {
phone: test_send_phone_number,
messageId: message.source_id
}.to_json
body: { phone: phone, messageId: message_source_id }.to_json
)
.to_return(status: 200)
result = service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
result = service.send_read_message(phone, message_source_id)
expect(result).to be(true)
end
end
context 'when response is unsuccessful' do
it 'raises ProviderUnavailableError' do
it 'logs the error and returns false' do
stub_request(:post, "#{api_instance_path_with_token}/read-message")
.with(headers: stub_headers)
.to_return(status: 400, body: 'error message', headers: {})
.to_return(status: 400, body: 'error message')
allow(Rails.logger).to receive(:error)
expect do
service.read_messages(messages, recipient_id: "+#{test_send_phone_number}")
end.to raise_error(Whatsapp::Providers::WhatsappZapiService::ProviderUnavailableError)
result = service.send_read_message(phone, message_source_id)
expect(result).to be(false)
expect(Rails.logger).to have_received(:error).with('error message')
end
end
end