Files
chatwoot/spec/services/conversations/unread_counts/counter_spec.rb
Sony MathewandGitHub 66cfb26c77 feat: Add unread count filters feature flag (1/6) (#14885)
## Description

Adds the account-level `unread_count_for_filters` feature flag as the
dark-launch gate for filtered sidebar unread counts. This reuses the
deprecated `quoted_email_reply` flag slot, resets the reused bit for
existing accounts, and removes stale defaults so new accounts do not
reference the old flag.

This also adds the feature where we are now calculating the unread counts for built in filters like mentions, participating and unattended along with unread count for saved filters/folders.

Closes
[CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders)

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
2026-07-08 23:50:40 +05:30

117 lines
4.6 KiB
Ruby

require 'rails_helper'
RSpec.describe Conversations::UnreadCounts::Counter do
let(:account) { create(:account) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:visible_inbox) { create(:inbox, account: account) }
let(:hidden_inbox) { create(:inbox, account: account) }
let(:label) { create(:label, account: account, title: 'billing', show_on_sidebar: true) }
let(:hidden_label) { create(:label, account: account, title: 'internal', show_on_sidebar: false) }
let(:visible_team) { create(:team, account: account, allow_auto_assign: false) }
let(:store) { Conversations::UnreadCounts::Store }
before do
create(:inbox_member, user: agent, inbox: visible_inbox)
create(:team_member, user: agent, team: visible_team)
end
after do
store.clear_account!(account.id)
end
it 'builds the base cache on demand' do
create_unread_conversation(account: account, inbox: visible_inbox, labels: [label.title], team: visible_team)
described_class.new(account: account, user: agent).perform
expect(store.base_ready?(account.id)).to be(true)
end
it 'uses a Redis lock while building the base cache on demand' do
lock_key = "UNREAD_CONVERSATIONS::V1::ACCOUNT::#{account.id}::BUILD_LOCK::BASE"
lock_manager = instance_double(Redis::LockManager)
allow(Redis::LockManager).to receive(:new).and_return(lock_manager)
allow(lock_manager).to receive(:with_lock).with(lock_key, described_class::BUILD_LOCK_TTL).and_yield.and_return(true)
create_unread_conversation(account: account, inbox: visible_inbox, labels: [label.title], team: visible_team)
described_class.new(account: account, user: agent).perform
expect(lock_manager).to have_received(:with_lock).with(lock_key, described_class::BUILD_LOCK_TTL)
end
it 'waits instead of rebuilding when another process owns the base build lock' do
lock_manager = instance_double(Redis::LockManager, with_lock: false)
counter = described_class.new(account: account, user: agent)
allow(Redis::LockManager).to receive(:new).and_return(lock_manager)
allow(counter).to receive(:wait_for_cache_ready) { store.mark_base_ready!(account.id) }
expect(Conversations::UnreadCounts::Builder).not_to receive(:new)
counter.perform
expect(counter).to have_received(:wait_for_cache_ready)
expect(store.base_ready?(account.id)).to be(true)
end
it 'counts unread conversations only across inboxes visible to a normal agent' do
create_unread_conversation(account: account, inbox: visible_inbox, labels: [label.title], team: visible_team)
create_unread_conversation(account: account, inbox: hidden_inbox, labels: [label.title], team: visible_team)
result = described_class.new(account: account, user: agent).perform
expect(result).to eq(
all_count: 1,
inboxes: { visible_inbox.id.to_s => 1 },
labels: { label.id.to_s => 1 },
teams: { visible_team.id.to_s => 1 }
)
end
it 'counts unread conversations across all account inboxes for admins' do
create_unread_conversation(account: account, inbox: visible_inbox, labels: [label.title], team: visible_team)
create_unread_conversation(account: account, inbox: hidden_inbox, labels: [label.title], team: visible_team)
result = described_class.new(account: account, user: admin).perform
expect(result).to eq(
all_count: 2,
inboxes: { visible_inbox.id.to_s => 1, hidden_inbox.id.to_s => 1 },
labels: { label.id.to_s => 2 },
teams: { visible_team.id.to_s => 2 }
)
end
it 'does not return zero counts or labels hidden from the sidebar' do
create_unread_conversation(account: account, inbox: visible_inbox, labels: [hidden_label.title], team: visible_team)
result = described_class.new(account: account, user: agent).perform
expect(result).to eq(
all_count: 1,
inboxes: { visible_inbox.id.to_s => 1 },
labels: {},
teams: { visible_team.id.to_s => 1 }
)
end
it 'merges filtered counts when the filtered count feature is enabled' do
account.enable_features!(:unread_count_for_filters)
filtered_counter = instance_double(
Conversations::UnreadCounts::FilteredCounter,
perform: { mentions_count: 1, participating_count: 2, unattended_count: 3, folders: { '4' => 5 } }
)
allow(Conversations::UnreadCounts::FilteredCounter).to receive(:new).and_return(filtered_counter)
result = described_class.new(account: account, user: agent).perform
expect(result).to include(
mentions_count: 1,
participating_count: 2,
unattended_count: 3,
folders: { '4' => 5 }
)
end
end