Files
moreminimore-chat/spec/models/email_template_spec.rb
Sony Mathew 65499df6a3 fix(email): allow larger email templates (#15146)
## Description

Branded email layouts and other email templates can now contain up to
262,144 characters instead of the generic 20,000-character text limit.
This supports customer layouts around 41 KB and 100 KB while retaining a
defined application-level ceiling. The account and inbox API schemas now
expose the same maximum.

## Closes


[CW-7682](https://linear.app/chatwoot/issue/CW-7682/allow-larger-branded-email-templates)

Related:
[CW-7514](https://linear.app/chatwoot/issue/CW-7514/branded-html-email-templates-per-inboxbrand)

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] This change requires a documentation update

## How Has This Been Tested?

1. As an administrator with `branded_email_templates` enabled, update an
account branded layout with a 100 KB Liquid layout containing `{{
content_for_layout }}` and confirm the request succeeds.
2. Update an Email inbox layout with more than 262,144 characters and
confirm the API returns `422`.
3. Confirm a layout at exactly 262,144 characters remains valid.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
2026-07-24 14:00:01 +05:30

144 lines
5.7 KiB
Ruby

require 'rails_helper'
RSpec.describe EmailTemplate do
describe 'validations' do
it 'allows the same layout name across installation, account, and inbox scopes' do
account = create(:account)
inbox = create(:inbox, :with_email, account: account)
create(:email_template, :layout, account: nil)
create(:email_template, :layout, account: account)
inbox_template = build(:email_template, :layout, account: account, inbox: inbox)
expect(inbox_template).to be_valid
end
it 'allows an account-scoped layout after an inbox-scoped layout' do
account = create(:account)
inbox = create(:inbox, :with_email, account: account)
create(:email_template, :layout, account: account, inbox: inbox)
account_template = build(:email_template, :layout, account: account)
expect(account_template).to be_valid
end
it 'allows an installation-scoped layout after account and inbox-scoped layouts' do
account = create(:account)
inbox = create(:inbox, :with_email, account: account)
create(:email_template, :layout, account: account)
create(:email_template, :layout, account: account, inbox: inbox)
installation_template = build(:email_template, :layout, account: nil)
expect(installation_template).to be_valid
end
it 'rejects duplicate installation-scoped templates' do
create(:email_template)
duplicate_template = build(:email_template)
expect(duplicate_template).not_to be_valid
expect(duplicate_template.errors[:name]).to include('has already been taken')
end
it 'rejects duplicate account-scoped templates' do
account = create(:account)
create(:email_template, account: account)
duplicate_template = build(:email_template, account: account)
expect(duplicate_template).not_to be_valid
expect(duplicate_template.errors[:name]).to include('has already been taken')
end
it 'rejects duplicate inbox-scoped templates' do
account = create(:account)
inbox = create(:inbox, :with_email, account: account)
create(:email_template, account: account, inbox: inbox)
duplicate_template = build(:email_template, account: account, inbox: inbox)
expect(duplicate_template).not_to be_valid
expect(duplicate_template.errors[:name]).to include('has already been taken')
end
it 'requires branded layouts to include content_for_layout' do
template = build(:email_template, name: EmailTemplate::BRANDED_LAYOUT_NAME, template_type: :layout, body: '<html><body>No slot</body></html>')
expect(template).not_to be_valid
expect(template.errors[:body]).to include('must include {{ content_for_layout }}')
end
it 'allows email templates up to 262,144 characters' do
slot = '{{ content_for_layout }}'
body = "#{'a' * (described_class::MAX_BODY_LENGTH - slot.length)}#{slot}"
template = build(:email_template, :layout, account: create(:account), body: body)
expect(template).to be_valid
end
it 'rejects email templates larger than 262,144 characters' do
slot = '{{ content_for_layout }}'
body = "#{'a' * (described_class::MAX_BODY_LENGTH - slot.length + 1)}#{slot}"
template = build(:email_template, :layout, account: create(:account), body: body)
expect(template).not_to be_valid
expect(template.errors[:body]).to include('is too long (maximum is 262144 characters)')
end
it 'validates liquid syntax' do
template = build(:email_template, body: '{{ broken ')
expect(template).not_to be_valid
expect(template.errors[:body].first).to include('has invalid Liquid syntax')
end
it 'requires account to match inbox account when both are present' do
inbox = create(:inbox, :with_email)
other_account = create(:account)
template = build(:email_template, :layout, account: other_account, inbox: inbox)
expect(template).not_to be_valid
expect(template.errors[:account]).to include('must match inbox account')
end
end
describe '.branded_layout_for' do
it 'uses inbox, account, then installation fallback order' do
account = create(:account)
inbox = create(:inbox, :with_email, account: account)
create(:email_template, :layout, body: 'Global {{ content_for_layout }}')
account_template = create(:email_template, :layout, account: account, body: 'Account {{ content_for_layout }}')
expect(described_class.branded_layout_for(inbox: inbox, account: account, locale: :en).id).to eq(account_template.id)
inbox_template = create(:email_template, :layout, account: account, inbox: inbox, body: 'Inbox {{ content_for_layout }}')
expect(described_class.branded_layout_for(inbox: inbox, account: account, locale: :en).id).to eq(inbox_template.id)
end
end
describe '.update_account_branded_layout!' do
it 'creates and updates the account-scoped branded layout' do
account = create(:account)
described_class.update_account_branded_layout!(account: account, body: 'Account {{ content_for_layout }}')
template = described_class.account_branded_layout_template_for(account)
expect(template.body).to eq('Account {{ content_for_layout }}')
described_class.update_account_branded_layout!(account: account, body: 'Updated {{ content_for_layout }}')
expect(template.reload.body).to eq('Updated {{ content_for_layout }}')
end
it 'clears the account-scoped branded layout for blank bodies' do
account = create(:account)
create(:email_template, :layout, account: account)
described_class.update_account_branded_layout!(account: account, body: '')
expect(described_class.account_branded_layout_template_for(account)).to be_nil
end
end
end