Merge branch 'hotfix/4.11.2' into develop

This commit is contained in:
Shivam Mishra
2026-03-09 21:20:08 +05:30
10 changed files with 269 additions and 23 deletions

View File

@@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe CustomAttributeDefinition do
let(:account) { create(:account) }
describe 'validations' do
describe 'attribute_key format' do
it 'allows alphanumeric keys with underscores' do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'order_date_1')
expect(cad).to be_valid
end
it 'allows hyphens and dots' do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'order-date.v2')
expect(cad).to be_valid
end
it 'allows Unicode letters' do
cad = build(:custom_attribute_definition, account: account, attribute_key: '客户类型')
expect(cad).to be_valid
end
it 'rejects keys with single quotes' do
cad = build(:custom_attribute_definition, account: account, attribute_key: "x'||(SELECT 1)||'")
expect(cad).not_to be_valid
expect(cad.errors[:attribute_key]).to be_present
end
it 'rejects keys with spaces' do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'order date')
expect(cad).not_to be_valid
end
it 'rejects keys with semicolons' do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'key; DROP TABLE users--')
expect(cad).not_to be_valid
end
it 'rejects keys with parentheses' do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'key()')
expect(cad).not_to be_valid
end
end
end
describe 'callbacks' do
describe '#strip_attribute_key' do
it 'strips leading and trailing whitespace from attribute_key' do
cad = create(:custom_attribute_definition, account: account, attribute_key: ' order_date ')
expect(cad.attribute_key).to eq('order_date')
end
it 'strips leading and trailing whitespace from attribute_display_name' do
cad = create(:custom_attribute_definition, account: account, attribute_display_name: ' Order Date ')
expect(cad.attribute_display_name).to eq('Order Date')
end
end
end
end

View File

@@ -49,6 +49,11 @@ describe Contacts::FilterService do
account: account,
attribute_model: 'contact_attribute',
attribute_display_type: 'date')
create(:custom_attribute_definition,
attribute_key: 'lifetime_value',
account: account,
attribute_model: 'contact_attribute',
attribute_display_type: 'number')
end
describe '#perform' do
@@ -60,7 +65,7 @@ describe Contacts::FilterService do
en_contact.update!(custom_attributes: { contact_additional_information: 'test custom data' })
el_contact.update!(custom_attributes: { contact_additional_information: 'test custom data', customer_type: 'platinum' })
cs_contact.update!(custom_attributes: { customer_type: 'platinum', signed_in_at: '2022-01-19' })
cs_contact.update!(custom_attributes: { customer_type: 'platinum', signed_in_at: '2022-01-19', lifetime_value: '120.50' })
end
context 'with standard attributes - name' do
@@ -272,6 +277,39 @@ describe Contacts::FilterService do
expect(result[:contacts].pluck(:id)).to include(cs_contact.id)
expect(result[:contacts].pluck(:id)).not_to include(en_contact.id)
end
it 'binds last_activity_at comparison values as dates' do
date_value = '2024-01-01'
params[:payload] = [
{
attribute_key: 'last_activity_at',
filter_operator: 'is_greater_than',
values: [date_value],
query_operator: nil
}.with_indifferent_access
]
service = filter_service.new(account, first_user, params)
filters = service.instance_variable_get(:@filters)['contacts']
condition_query = service.send(:build_condition_query, filters, params[:payload].first, 0)
expect(condition_query).to include('(contacts.last_activity_at)::date > :value_0')
expect(service.instance_variable_get(:@filter_values)['value_0']).to eq(Date.iso8601(date_value))
end
it 'rejects invalid last_activity_at comparison values' do
malicious_value = "2024-01-01'::date OR (SELECT pg_sleep(5)) IS NOT NULL --"
params[:payload] = [
{
attribute_key: 'last_activity_at',
filter_operator: 'is_greater_than',
values: [malicious_value],
query_operator: nil
}.with_indifferent_access
]
expect { filter_service.new(account, first_user, params).perform }.to raise_error(CustomExceptions::CustomFilter::InvalidValue)
end
end
context 'with additional attributes' do
@@ -369,6 +407,72 @@ describe Contacts::FilterService do
expect(result[:contacts].length).to be expected_count
expect(result[:contacts].pluck(:id)).to include(el_contact.id)
end
it 'binds custom date comparison values as dates' do
date_value = '2024-01-01'
params[:payload] = [
{
attribute_key: 'signed_in_at',
filter_operator: 'is_less_than',
values: [date_value],
query_operator: nil
}.with_indifferent_access
]
service = filter_service.new(account, first_user, params)
filters = service.instance_variable_get(:@filters)['contacts']
condition_query = service.send(:build_condition_query, filters, params[:payload].first, 0)
expect(condition_query).to include("(contacts.custom_attributes ->> 'signed_in_at')::date < :value_0")
expect(service.instance_variable_get(:@filter_values)['value_0']).to eq(Date.iso8601(date_value))
end
it 'binds custom numeric comparison values as decimals' do
params[:payload] = [
{
attribute_key: 'lifetime_value',
filter_operator: 'is_greater_than',
values: ['100.25'],
query_operator: nil
}.with_indifferent_access
]
service = filter_service.new(account, first_user, params)
filters = service.instance_variable_get(:@filters)['contacts']
condition_query = service.send(:build_condition_query, filters, params[:payload].first, 0)
expect(condition_query).to include("(contacts.custom_attributes ->> 'lifetime_value')::numeric > :value_0")
expect(service.instance_variable_get(:@filter_values)['value_0']).to eq(BigDecimal('100.25'))
end
it 'filters by custom numeric attributes' do
params[:payload] = [
{
attribute_key: 'lifetime_value',
filter_operator: 'is_greater_than',
values: ['100.25'],
query_operator: nil
}.with_indifferent_access
]
result = filter_service.new(account, first_user, params).perform
expect(result[:contacts].pluck(:id)).to eq([cs_contact.id])
end
it 'rejects invalid custom date comparison values' do
malicious_value = "2024-01-01'::date OR (SELECT pg_sleep(5)) IS NOT NULL --"
params[:payload] = [
{
attribute_key: 'signed_in_at',
filter_operator: 'is_less_than',
values: [malicious_value],
query_operator: nil
}.with_indifferent_access
]
expect { filter_service.new(account, first_user, params).perform }.to raise_error(CustomExceptions::CustomFilter::InvalidValue)
end
end
end
end

View File

@@ -417,6 +417,41 @@ describe Conversations::FilterService do
expect(result[:conversations].length).to be expected_count
end
it 'binds created_at comparison values as dates' do
date_value = '2024-01-01'
params[:payload] = [
{
attribute_key: 'created_at',
filter_operator: 'is_greater_than',
values: [date_value],
query_operator: nil,
custom_attribute_type: ''
}.with_indifferent_access
]
service = filter_service.new(params, user_1, account)
filters = service.instance_variable_get(:@filters)['conversations']
condition_query = service.send(:build_condition_query, filters, params[:payload].first, 0)
expect(condition_query).to include('(conversations.created_at)::date > :value_0')
expect(service.instance_variable_get(:@filter_values)['value_0']).to eq(Date.iso8601(date_value))
end
it 'rejects invalid created_at comparison values' do
malicious_value = "2024-01-01'::date OR (SELECT pg_sleep(5)) IS NOT NULL --"
params[:payload] = [
{
attribute_key: 'created_at',
filter_operator: 'is_greater_than',
values: [malicious_value],
query_operator: nil,
custom_attribute_type: ''
}.with_indifferent_access
]
expect { filter_service.new(params, user_1, account).perform }.to raise_error(CustomExceptions::CustomFilter::InvalidValue)
end
it 'filter by created_at and conversation_type' do
params[:payload] = [
{