Merge branch 'develop' into feature/cw-7513

This commit is contained in:
Tanmay Deep Sharma
2026-07-13 14:36:26 +05:30
55 changed files with 1547 additions and 121 deletions
@@ -140,6 +140,51 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
expect(json_response['messages'][0]['content']).to eq 'This is a test message'
end
it 'saves contact custom attributes on the widget contact' do
post '/api/v1/widget/conversations',
headers: { 'X-Auth-Token' => token },
params: {
website_token: web_widget.website_token,
contact: {
name: 'contact-name',
email: 'contact-email@chatwoot.com',
custom_attributes: { cpf: '123.456.789-09' }
},
message: {
content: 'This is a test message'
}
},
as: :json
expect(response).to have_http_status(:success)
expect(contact.reload.custom_attributes['cpf']).to eq('123.456.789-09')
end
it 'saves contact custom attributes on the surviving contact when merged into an existing contact' do
existing_contact = create(:contact, account: account, email: 'contact-email@chatwoot.com', custom_attributes: { 'cpf' => 'old-value' })
post '/api/v1/widget/conversations',
headers: { 'X-Auth-Token' => token },
params: {
website_token: web_widget.website_token,
contact: {
name: 'contact-name',
email: existing_contact.email,
custom_attributes: { cpf: '123.456.789-09' }
},
message: {
content: 'This is a test message'
}
},
as: :json
expect(response).to have_http_status(:success)
# the widget contact is merged into the existing contact; the freshly
# submitted value must land on the surviving contact and win over stale data
expect(Contact.exists?(contact.id)).to be(false)
expect(existing_contact.reload.custom_attributes['cpf']).to eq('123.456.789-09')
end
it 'doesnt not add phone number if the invalid phone number is provided' do
existing_contact = create(:contact, account: account)
-1
View File
@@ -108,7 +108,6 @@ RSpec.describe Account do
it 'configures the account feature flag extension column' do
expect(described_class.flag_columns).to include('feature_flags', 'feature_flags_ext_1')
expect(described_class.flag_mapping['feature_flags_ext_1']).to eq({})
end
it 'keeps existing feature flags on the original column' do
+10
View File
@@ -42,8 +42,18 @@ RSpec.describe Channel::Whatsapp do
body: { data: [{
id: '123456789', name: 'test_template'
}] }.to_json)
stub_request(:get, 'https://graph.facebook.com/v14.0//phone_numbers?fields=id&limit=100&access_token=test_key')
.to_return(status: 200, body: { data: [{ id: 'random_id' }] }.to_json, headers: { 'Content-Type' => 'application/json' })
expect(channel.save).to be(true)
end
it 'validates false when phone number id is wrong' do
stub_request(:get, 'https://graph.facebook.com/v14.0//message_templates?access_token=test_key')
.to_return(status: 200, body: { data: [] }.to_json)
stub_request(:get, 'https://graph.facebook.com/v14.0//phone_numbers?fields=id&limit=100&access_token=test_key')
.to_return(status: 200, body: { data: [{ id: 'another_phone_id' }] }.to_json, headers: { 'Content-Type' => 'application/json' })
expect(channel.save).to be(false)
end
end
describe 'webhook_verify_token' do
@@ -239,6 +239,24 @@ RSpec.describe AutoAssignment::AssignmentService do
expect(assigned_count).to eq(1)
expect(old_conversation.reload.assignee).to eq(agent)
end
context 'when the inbox has no assignment policy' do
before do
inbox.inbox_assignment_policy.destroy!
inbox.reload
end
it 'falls back to the default threshold and skips stale conversations' do
stale_conversation = create(:conversation, inbox: inbox, assignee: nil, last_activity_at: 8.days.ago)
recent_conversation = create(:conversation, inbox: inbox, assignee: nil, last_activity_at: 6.days.ago)
assigned_count = service.perform_bulk_assignment(limit: 10)
expect(assigned_count).to eq(1)
expect(stale_conversation.reload.assignee).to be_nil
expect(recent_conversation.reload.assignee).to eq(agent)
end
end
end
context 'with fair distribution' do
@@ -96,7 +96,14 @@ RSpec.describe Conversations::UnreadCounts::FilteredCountStore do
described_class.bump_built_in_filter_version!(account_id: account_id, user_id: user_id)
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id, now: built_at + 2.minutes)).to be_stale
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id, now: built_at + 36.minutes)).to be_expired
expect(
described_class.built_in_filter_counts_state(
account_id: account_id,
user_id: user_id,
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_FRESH_TTL +
Conversations::UnreadCounts::FILTERED_COUNT_STALE_WINDOW + 1.second
)
).to be_expired
Redis::Alfred.delete(described_class.built_in_filter_counts_key(account_id, user_id))
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id)).to be_missing
@@ -202,8 +209,18 @@ RSpec.describe Conversations::UnreadCounts::FilteredCountStore do
)
snapshot = described_class.built_in_filter_counts(account_id: account_id, user_id: user_id)
expect(described_class.refresh_due?(snapshot, now: built_at + 10.seconds)).to be(false)
expect(described_class.refresh_due?(snapshot, now: built_at + 31.seconds)).to be(true)
expect(
described_class.refresh_due?(
snapshot,
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL - 1.second
)
).to be(false)
expect(
described_class.refresh_due?(
snapshot,
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
)
).to be(true)
expect(described_class.claim_built_in_filter_refresh!(account_id: account_id, user_id: user_id)).to be(true)
expect(described_class.claim_built_in_filter_refresh!(account_id: account_id, user_id: user_id)).to be(false)
@@ -48,10 +48,22 @@ RSpec.describe Conversations::UnreadCounts::FilteredCounter do
create(:mention, account: account, conversation: second_mention, user: agent)
store.bump_conversation_version!(account.id)
expect(described_class.new(account: account, user: agent, now: now + 10.seconds).perform[:mentions_count]).to eq(1)
expect(
described_class.new(
account: account,
user: agent,
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL - 1.second
).perform[:mentions_count]
).to eq(1)
Redis::Alfred.delete(store.built_in_filter_refresh_throttle_key(account.id, agent.id))
expect(described_class.new(account: account, user: agent, now: now + 31.seconds).perform[:mentions_count]).to eq(2)
expect(
described_class.new(
account: account,
user: agent,
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
).perform[:mentions_count]
).to eq(2)
end
it 'returns stale built-in counts when a refresh build hits a database error' do
@@ -62,7 +74,11 @@ RSpec.describe Conversations::UnreadCounts::FilteredCounter do
store.bump_conversation_version!(account.id)
Redis::Alfred.delete(store.built_in_filter_refresh_throttle_key(account.id, agent.id))
failing_counter = described_class.new(account: account, user: agent, now: now + 31.seconds)
failing_counter = described_class.new(
account: account,
user: agent,
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
)
allow(failing_counter).to receive(:built_in_counts_from_database).and_raise(ActiveRecord::StatementInvalid.new('statement timeout'))
expect(failing_counter.perform[:mentions_count]).to eq(1)