From ded86b9ed1c8cb46df7dcef1e9f774c1af42a505 Mon Sep 17 00:00:00 2001 From: Muhsin <12408980+muhsin-k@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:20:05 +0400 Subject: [PATCH] fix(whatsapp): harden health sync failure handling --- .../dashboard/settings/inbox/Settings.vue | 1 - .../inbox/components/AccountHealth.vue | 14 ++++++++---- .../components/specs/AccountHealth.spec.js | 16 ++++++++++++++ app/jobs/channels/whatsapp/health_sync_job.rb | 2 ++ ...phone_number_health_to_channel_whatsapp.rb | 2 +- db/schema.rb | 2 +- .../channels/whatsapp/health_sync_job_spec.rb | 22 +++++++++++++++++++ 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 9d96430d2..8c4b280ac 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -587,7 +587,6 @@ export default { this.healthData = response.data; } catch (error) { const apiError = error.response?.data?.error; - this.healthData = null; this.healthError = typeof apiError === 'object' ? apiError diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AccountHealth.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AccountHealth.vue index f902d45d9..adbf9d0c1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AccountHealth.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AccountHealth.vue @@ -335,11 +335,17 @@ const handleGoToSettings = () => { const getQualityRatingTextColor = rating => QUALITY_COLORS[rating] || QUALITY_COLORS.UNKNOWN; -const formatTierDisplay = tier => - t(`INBOX_MGMT.ACCOUNT_HEALTH.VALUES.TIERS.${tier}`) || tier; +const formatTierDisplay = tier => { + const translationKey = `INBOX_MGMT.ACCOUNT_HEALTH.VALUES.TIERS.${tier}`; -const formatModeDisplay = mode => - t(`INBOX_MGMT.ACCOUNT_HEALTH.VALUES.MODES.${mode}`) || mode; + return te(translationKey) ? t(translationKey) : formatStatusDisplay(tier); +}; + +const formatModeDisplay = mode => { + const translationKey = `INBOX_MGMT.ACCOUNT_HEALTH.VALUES.MODES.${mode}`; + + return te(translationKey) ? t(translationKey) : formatStatusDisplay(mode); +}; const getModeStatusTextColor = mode => MODE_COLORS[mode] || 'text-n-slate-12'; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/specs/AccountHealth.spec.js b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/specs/AccountHealth.spec.js index 2258b6654..d771318e4 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/specs/AccountHealth.spec.js +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/specs/AccountHealth.spec.js @@ -50,4 +50,20 @@ describe('AccountHealth', () => { '_blank' ); }); + + it('formats unknown messaging tiers and account modes without exposing translation keys', () => { + const wrapper = mountComponent({ + messaging_limit_tier: 'TIER_CUSTOM', + account_mode: 'CUSTOM_MODE', + }); + + expect(wrapper.text()).toContain('Tier Custom'); + expect(wrapper.text()).toContain('Custom Mode'); + expect(wrapper.text()).not.toContain( + 'INBOX_MGMT.ACCOUNT_HEALTH.VALUES.TIERS.TIER_CUSTOM' + ); + expect(wrapper.text()).not.toContain( + 'INBOX_MGMT.ACCOUNT_HEALTH.VALUES.MODES.CUSTOM_MODE' + ); + }); }); diff --git a/app/jobs/channels/whatsapp/health_sync_job.rb b/app/jobs/channels/whatsapp/health_sync_job.rb index b6e09818a..258aac7e7 100644 --- a/app/jobs/channels/whatsapp/health_sync_job.rb +++ b/app/jobs/channels/whatsapp/health_sync_job.rb @@ -3,5 +3,7 @@ class Channels::Whatsapp::HealthSyncJob < ApplicationJob def perform(whatsapp_channel) Whatsapp::HealthService.new(whatsapp_channel).sync_health_status! + rescue Whatsapp::HealthService::ApiError, ArgumentError + nil end end diff --git a/db/migrate/20260718000000_add_phone_number_health_to_channel_whatsapp.rb b/db/migrate/20260718000000_add_phone_number_health_to_channel_whatsapp.rb index 687bcda3f..58ce84983 100644 --- a/db/migrate/20260718000000_add_phone_number_health_to_channel_whatsapp.rb +++ b/db/migrate/20260718000000_add_phone_number_health_to_channel_whatsapp.rb @@ -2,7 +2,7 @@ class AddPhoneNumberHealthToChannelWhatsapp < ActiveRecord::Migration[7.1] def change add_column :channel_whatsapp, :phone_number_health, :jsonb, default: {}, null: false add_column :channel_whatsapp, :phone_number_health_checked_at, :datetime - add_column :channel_whatsapp, :phone_number_health_error, :string + add_column :channel_whatsapp, :phone_number_health_error, :string, limit: 500 add_index :channel_whatsapp, :phone_number_health_checked_at end end diff --git a/db/schema.rb b/db/schema.rb index b23e81786..c7c5f6a77 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -685,7 +685,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_18_000000) do t.datetime "message_templates_last_updated", precision: nil t.jsonb "phone_number_health", default: {}, null: false t.datetime "phone_number_health_checked_at" - t.string "phone_number_health_error" + t.string "phone_number_health_error", limit: 500 t.index ["phone_number_health_checked_at"], name: "index_channel_whatsapp_on_phone_number_health_checked_at" t.index ["phone_number"], name: "index_channel_whatsapp_on_phone_number", unique: true end diff --git a/spec/jobs/channels/whatsapp/health_sync_job_spec.rb b/spec/jobs/channels/whatsapp/health_sync_job_spec.rb index 1a1c4eede..aeb1e6eb6 100644 --- a/spec/jobs/channels/whatsapp/health_sync_job_spec.rb +++ b/spec/jobs/channels/whatsapp/health_sync_job_spec.rb @@ -23,4 +23,26 @@ RSpec.describe Channels::Whatsapp::HealthSyncJob do expect(health_service).to have_received(:sync_health_status!) end + + it 'does not retry recorded API failures' do + error = Whatsapp::HealthService::ApiError.new(message: 'Request failed', http_status: 400) + allow(Whatsapp::HealthService).to receive(:new).with(whatsapp_channel).and_return(health_service) + allow(health_service).to receive(:sync_health_status!).and_raise(error) + + expect { described_class.perform_now(whatsapp_channel) }.not_to raise_error + end + + it 'does not retry recorded configuration failures' do + allow(Whatsapp::HealthService).to receive(:new).with(whatsapp_channel).and_return(health_service) + allow(health_service).to receive(:sync_health_status!).and_raise(ArgumentError, 'API key is missing') + + expect { described_class.perform_now(whatsapp_channel) }.not_to raise_error + end + + it 'retries unexpected failures' do + allow(Whatsapp::HealthService).to receive(:new).with(whatsapp_channel).and_return(health_service) + allow(health_service).to receive(:sync_health_status!).and_raise(StandardError, 'Database unavailable') + + expect { described_class.perform_now(whatsapp_channel) }.to raise_error(StandardError, 'Database unavailable') + end end