fix: prevent deletion of scheduled messages that have been sent or failed (#212)

* fix: prevent deletion of scheduled messages that have been sent or failed

* fix: update error message for deletion of processed scheduled messages
This commit is contained in:
Gabriel Jablonski 2026-02-05 18:42:46 -03:00 committed by GitHub
parent c24eba9180
commit 3c47ea3d43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 57 additions and 16 deletions

View File

@ -27,11 +27,13 @@ class Api::V1::Accounts::Conversations::ScheduledMessagesController < Api::V1::A
end end
def destroy def destroy
if @scheduled_message.sent? || @scheduled_message.failed?
return render json: { error: I18n.t('errors.scheduled_messages.cannot_delete_processed') }, status: :unprocessable_entity
end
scheduled_message = @scheduled_message scheduled_message = @scheduled_message
scheduled_message.destroy! scheduled_message.destroy!
dispatch_event(SCHEDULED_MESSAGE_DELETED, scheduled_message: scheduled_message) dispatch_event(SCHEDULED_MESSAGE_DELETED, scheduled_message: scheduled_message)
rescue ActiveRecord::RecordNotDestroyed => e
render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity
end end
private private

View File

@ -51,7 +51,6 @@ class ScheduledMessage < ApplicationRecord
enum status: { draft: 0, pending: 1, sent: 2, failed: 3 } enum status: { draft: 0, pending: 1, sent: 2, failed: 3 }
before_validation :process_message_variables, if: :content_changed? before_validation :process_message_variables, if: :content_changed?
before_destroy :prevent_destroy_if_processed
validates :scheduled_at, presence: true, unless: -> { status == 'draft' } validates :scheduled_at, presence: true, unless: -> { status == 'draft' }
validates :content, presence: true, unless: :content_optional? validates :content, presence: true, unless: :content_optional?
@ -121,13 +120,6 @@ class ScheduledMessage < ApplicationRecord
changed_attributes.keys == ['status'] changed_attributes.keys == ['status']
end end
def prevent_destroy_if_processed
return unless sent? || failed?
errors.add(:base, 'Cannot delete a scheduled message that has already been sent or failed')
throw(:abort)
end
def scheduled_at_must_be_in_future def scheduled_at_must_be_in_future
return if scheduled_at.blank? return if scheduled_at.blank?
return if scheduled_at > Time.current return if scheduled_at > Time.current

View File

@ -68,6 +68,8 @@ en:
scheduled_message: scheduled_message:
delay_out_of_range: Delay must be between 1 minute and 999 days delay_out_of_range: Delay must be between 1 minute and 999 days
content_attachment_or_template_required: Scheduled message must have content, attachment, or template content_attachment_or_template_required: Scheduled message must have content, attachment, or template
scheduled_messages:
cannot_delete_processed: Cannot delete a scheduled message that has already been sent or failed
saml: saml:
feature_not_enabled: SAML feature not enabled for this account feature_not_enabled: SAML feature not enabled for this account
sso_not_enabled: SAML SSO is not enabled for this installation sso_not_enabled: SAML SSO is not enabled for this installation

View File

@ -114,6 +114,19 @@ RSpec.describe 'Scheduled Messages API', type: :request do
expect(ScheduledMessage.exists?(scheduled_message.id)).to be(false) expect(ScheduledMessage.exists?(scheduled_message.id)).to be(false)
end end
it 'deletes pending scheduled messages with attachments' do
scheduled_message.attachment.attach(
io: Rails.root.join('spec/assets/avatar.png').open,
filename: 'avatar.png',
content_type: 'image/png'
)
delete scheduled_message_url(scheduled_message), headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:success)
expect(ScheduledMessage.exists?(scheduled_message.id)).to be(false)
end
it 'rejects delete for sent messages' do it 'rejects delete for sent messages' do
scheduled_message.update!(status: :sent) scheduled_message.update!(status: :sent)

View File

@ -182,20 +182,52 @@ RSpec.describe ScheduledMessage, type: :model do
expect { scheduled_message.destroy }.to change(described_class, :count).by(-1) expect { scheduled_message.destroy }.to change(described_class, :count).by(-1)
end end
it 'does not allow deleting sent messages' do it 'allows deleting pending messages with attachments' do
scheduled_message = create_scheduled_message
scheduled_message.attachment.attach(
io: Rails.root.join('spec/assets/avatar.png').open,
filename: 'avatar.png',
content_type: 'image/png'
)
expect { scheduled_message.destroy! }.to change(described_class, :count).by(-1)
end
it 'allows destroying conversation with pending scheduled messages with attachments' do
scheduled_message = create_scheduled_message
scheduled_message.attachment.attach(
io: Rails.root.join('spec/assets/avatar.png').open,
filename: 'avatar.png',
content_type: 'image/png'
)
expect { conversation.destroy! }.to change(described_class, :count).by(-1)
end
it 'allows destroying conversation with sent scheduled messages with attachments' do
scheduled_message = create_scheduled_message
scheduled_message.attachment.attach(
io: Rails.root.join('spec/assets/avatar.png').open,
filename: 'avatar.png',
content_type: 'image/png'
)
scheduled_message.update!(status: :sent)
expect { conversation.destroy! }.to change(described_class, :count).by(-1)
end
it 'allows deleting sent messages at model level (controller enforces restriction)' do
scheduled_message = create_scheduled_message scheduled_message = create_scheduled_message
scheduled_message.update!(status: :sent) scheduled_message.update!(status: :sent)
expect { scheduled_message.destroy }.not_to change(described_class, :count) expect { scheduled_message.destroy! }.to change(described_class, :count).by(-1)
expect(scheduled_message.errors[:base]).to include('Cannot delete a scheduled message that has already been sent or failed')
end end
it 'does not allow deleting failed messages' do it 'allows deleting failed messages at model level (controller enforces restriction)' do
scheduled_message = create_scheduled_message scheduled_message = create_scheduled_message
scheduled_message.update!(status: :failed) scheduled_message.update!(status: :failed)
expect { scheduled_message.destroy }.not_to change(described_class, :count) expect { scheduled_message.destroy! }.to change(described_class, :count).by(-1)
expect(scheduled_message.errors[:base]).to include('Cannot delete a scheduled message that has already been sent or failed')
end end
end end
end end