Files
chatwoot/spec/models/team_spec.rb
T
ce8c8e9a11 fix: sanitize control characters in team names (#14865)
Team names created via the API could contain control characters (for
example a trailing newline). Because the team-delete confirmation dialog
requires you to retype the team name and matches it against the stored
value, a hidden control character meant the typed name never matched —
leaving the team impossible to delete from the UI. This sanitizes team
names on save so they stay clean and deletable.

#### How to reproduce
1. Create a team via `POST /api/v1/accounts/{account_id}/teams` with
`{"name": "test\n"}`.
2. The team is created with the trailing newline stored in `name`.
3. In **Settings → Teams**, click delete and type the team name to
confirm — the match fails, so the team cannot be deleted.

#### What changed
- `app/models/team.rb`: the existing `before_validation` now strips
control characters and surrounding whitespace before downcasing the
name. Names that reduce to blank (e.g. only newlines/tabs) are rejected
loudly by the existing `presence` validation.
- Fixing at the model layer covers the API and every other create/update
path, rather than relying on the frontend confirm-dialog `.trim()`
(which only handles leading/trailing whitespace, not internal control
characters).

Note: this prevents new malformed names. Any team already saved with a
control character can be made deletable again simply by renaming it (an
update re-runs the same sanitization).

| Input | Stored as | Result |
|---|---|---|
| `"test\n"` | `"test"` | valid, deletable |
| `"te\nst"` (internal) | `"test"` | valid |
| `"\t\n "` (only control/ws) | — | rejected: "Name must not be blank" |
| `"Customer Support"` | `"customer support"` | unchanged behavior |

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:04:24 -07:00

82 lines
3.2 KiB
Ruby

require 'rails_helper'
RSpec.describe Team do
describe 'associations' do
it { is_expected.to belong_to(:account) }
it { is_expected.to have_many(:conversations) }
it { is_expected.to have_many(:team_members) }
end
describe 'name normalization' do
let(:account) { create(:account) }
it 'downcases the name' do
team = create(:team, account: account, name: 'Customer Support')
expect(team.name).to eq('customer support')
end
it 'strips control characters and surrounding whitespace' do
team = create(:team, account: account, name: " Sales\n")
expect(team.name).to eq('sales')
end
it 'removes control characters embedded within the name' do
team = create(:team, account: account, name: "su\npport")
expect(team.name).to eq('support')
end
it 'is invalid when the name reduces to blank after sanitization' do
team = build(:team, account: account, name: "\t\n ")
expect(team).not_to be_valid
expect(team.errors[:name]).to include(I18n.t('errors.validations.presence'))
end
end
describe '#add_members' do
let(:team) { FactoryBot.create(:team) }
before do
allow(Rails.configuration.dispatcher).to receive(:dispatch)
end
it 'handles adds all members and resets cache keys' do
users = FactoryBot.create_list(:user, 3)
team.add_members(users.map(&:id))
expect(team.reload.team_members.size).to eq(3)
expect(Rails.configuration.dispatcher).to have_received(:dispatch).at_least(:once)
.with(
'account.cache_invalidated',
kind_of(Time),
account: team.account,
cache_keys: team.account.cache_keys
)
end
end
describe '#remove_members' do
let(:team) { FactoryBot.create(:team) }
let(:users) { FactoryBot.create_list(:user, 3) }
before do
team.add_members(users.map(&:id))
allow(Rails.configuration.dispatcher).to receive(:dispatch)
end
it 'removes the members and resets cache keys' do
expect(team.reload.team_members.size).to eq(3)
team.remove_members(users.map(&:id))
expect(team.reload.team_members.size).to eq(0)
expect(Rails.configuration.dispatcher).to have_received(:dispatch).at_least(:once)
.with(
'account.cache_invalidated',
kind_of(Time),
account: team.account,
cache_keys: team.account.cache_keys
)
end
end
end