Files
chatwoot/spec/services/labels/destroy_service_spec.rb
9c1d1c4070 feat(labels): remove label associations asynchronously on delete (#13531)
## Summary
- Remove label deletion dependency on association cleanup by deleting
immediately and enqueueing a background job.
- Add `Labels::RemoveAssociationsJob` to strip deleted label references
from tagged conversations and contacts.
- Keep this version simple by removing the label count/prompt
requirement requested.

## Implementation notes
- Enqueue job from `Api::V1::Accounts::LabelsController#destroy` with
label title + account id.
- Background work performed in `Labels::DestroyService`.

## References
- Linear issue:
https://linear.app/chatwoot/issue/CW-4765/cw-2857-enhancement-removing-labels-is-inconsistent
- GitHub issue: https://github.com/chatwoot/chatwoot/issues/1249

## Testing
- `bundle exec rspec
spec/controllers/api/v1/accounts/labels_controller_spec.rb
spec/services/labels/destroy_service_spec.rb
spec/jobs/labels/remove_associations_job_spec.rb
spec/services/labels/update_service_spec.rb`
- `bundle exec rubocop
app/controllers/api/v1/accounts/labels_controller.rb
app/jobs/labels/remove_associations_job.rb
spec/controllers/api/v1/accounts/labels_controller_spec.rb
spec/jobs/labels/remove_associations_job_spec.rb
spec/services/labels/destroy_service_spec.rb`

---------

Co-authored-by: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-05-08 13:40:36 +05:30

103 lines
3.4 KiB
Ruby

require 'rails_helper'
describe Labels::DestroyService do
let(:account) { create(:account) }
let(:conversation) { create(:conversation, account: account) }
let(:label) { create(:label, account: account) }
let(:contact) { conversation.contact }
let(:label_deleted_at) { Time.zone.parse('2026-05-07 10:00:00 UTC') }
before do
conversation.label_list.add(label.title)
conversation.label_list.add('billing')
conversation.save!
contact.label_list.add(label.title)
contact.label_list.add('vip')
contact.save!
set_label_tagging_created_at(conversation, label_deleted_at - 1.minute)
set_label_tagging_created_at(contact, label_deleted_at - 1.minute)
end
describe '#perform' do
it 'removes label from associated conversations and contacts' do
described_class.new(
label_title: label.title,
account_id: account.id,
label_deleted_at: label_deleted_at
).perform
expect(conversation.reload.label_list).to eq(['billing'])
expect(conversation.cached_label_list).to eq('billing')
expect(contact.reload.label_list).to eq(['vip'])
end
it 'removes label associations after the label record is destroyed' do
label_title = label.title
label.destroy!
described_class.new(
label_title: label_title,
account_id: account.id,
label_deleted_at: label_deleted_at
).perform
expect(conversation.reload.label_list).to eq(['billing'])
expect(conversation.cached_label_list).to eq('billing')
expect(contact.reload.label_list).to eq(['vip'])
end
it 'does not remove labels from other accounts' do
other_account = create(:account)
other_conversation = create(:conversation, account: other_account)
other_conversation.label_list.add(label.title)
other_conversation.save!
set_label_tagging_created_at(other_conversation, label_deleted_at - 1.minute)
described_class.new(
label_title: label.title,
account_id: account.id,
label_deleted_at: label_deleted_at
).perform
expect(other_conversation.reload.label_list).to eq([label.title])
end
it 'does not dispatch conversation or contact update events' do
expect(Rails.configuration.dispatcher).not_to receive(:dispatch)
described_class.new(
label_title: label.title,
account_id: account.id,
label_deleted_at: label_deleted_at
).perform
end
it 'does not remove label associations created after the label was deleted' do
other_conversation = create(:conversation, account: account)
other_conversation.label_list.add(label.title)
other_conversation.save!
set_label_tagging_created_at(other_conversation, label_deleted_at + 1.minute)
described_class.new(
label_title: label.title,
account_id: account.id,
label_deleted_at: label_deleted_at
).perform
expect(conversation.reload.label_list).to eq(['billing'])
expect(conversation.cached_label_list).to eq('billing')
expect(contact.reload.label_list).to eq(['vip'])
expect(other_conversation.reload.label_list).to eq([label.title])
end
end
def set_label_tagging_created_at(record, created_at)
ActsAsTaggableOn::Tagging
.joins(:tag)
.find_by!(context: 'labels', taggable: record, tags: { name: label.title })
.update!(created_at: created_at)
end
end