Implantação da geração de mensagem de forma livre e mudança na engine de mensagem
This commit is contained in:
43
spec/models/contato_spec.rb
Normal file
43
spec/models/contato_spec.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Contato do
|
||||
it 'normaliza o telefone digitado para E.164 na gravação' do
|
||||
# O ADM digita com máscara; mandar esse texto cru para o provedor devolve
|
||||
# erro de destino inválido que o rescue do envio esconderia.
|
||||
%w[(11)92005-1157 11920051157 011\ 92005\ 1157 +5511920051157].each do |digitado|
|
||||
contato = build(:contato, telefone: digitado)
|
||||
expect(contato).to be_valid
|
||||
expect(contato.telefone).to eq('+5511920051157')
|
||||
end
|
||||
end
|
||||
|
||||
it 'exige ao menos um canal' do
|
||||
contato = build(:contato, telefone: nil, email: nil)
|
||||
expect(contato).not_to be_valid
|
||||
expect(contato.errors.full_messages.join).to include('WhatsApp ou um e-mail')
|
||||
end
|
||||
|
||||
it 'aceita contato só de e-mail' do
|
||||
expect(build(:contato, telefone: nil, email: 'diretoria@reem.com')).to be_valid
|
||||
end
|
||||
|
||||
it 'rejeita telefone que não dá para normalizar, devolvendo o valor digitado' do
|
||||
contato = build(:contato, telefone: '123')
|
||||
expect(contato).not_to be_valid
|
||||
expect(contato.errors[:telefone].join).to include('número brasileiro')
|
||||
# O ADM precisa ver o que digitou para corrigir.
|
||||
expect(contato.telefone).to eq('123')
|
||||
end
|
||||
|
||||
describe '.com_canal' do
|
||||
let!(:so_zap) { create(:contato, telefone: '11 92005-1157', email: nil) }
|
||||
let!(:so_email) { create(:contato, telefone: nil, email: 'a@b.com') }
|
||||
let!(:ambos) { create(:contato, telefone: '11 93005-1157', email: 'c@d.com') }
|
||||
|
||||
it 'filtra por quem tem o canal' do
|
||||
expect(Contato.com_canal('whatsapp')).to contain_exactly(so_zap, ambos)
|
||||
expect(Contato.com_canal('email')).to contain_exactly(so_email, ambos)
|
||||
expect(Contato.com_canal('ambos')).to contain_exactly(so_zap, so_email, ambos)
|
||||
end
|
||||
end
|
||||
end
|
||||
69
spec/models/evento_notificacao_spec.rb
Normal file
69
spec/models/evento_notificacao_spec.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe EventoNotificacao do
|
||||
it 'gera a chave a partir do nome quando não informada' do
|
||||
evento = described_class.create!(nome: 'Aviso da Diretoria', gatilho: 'manual')
|
||||
expect(evento.chave).to eq('aviso_da_diretoria')
|
||||
end
|
||||
|
||||
it 'exige dia da semana na frequência semanal' do
|
||||
evento = build(:evento_notificacao, gatilho: 'agendado', frequencia: 'semanal', dia_semana: nil)
|
||||
expect(evento).not_to be_valid
|
||||
expect(evento.errors[:dia_semana]).to be_present
|
||||
end
|
||||
|
||||
it 'evento de sistema não é apagável' do
|
||||
expect(build(:evento_notificacao, sistema: true).apagavel?).to be(false)
|
||||
expect(build(:evento_notificacao, sistema: false).apagavel?).to be(true)
|
||||
end
|
||||
|
||||
describe '#destinatarios' do
|
||||
let(:evento) { create(:evento_notificacao) }
|
||||
let(:diretoria) { create(:grupo_contato) }
|
||||
let(:operacao) { create(:grupo_contato) }
|
||||
|
||||
it 'junta os contatos de todos os grupos assinantes, sem repetir' do
|
||||
a = create(:contato, grupo_contato: diretoria, telefone: '11 92005-1157')
|
||||
b = create(:contato, grupo_contato: operacao, telefone: '11 93005-1157')
|
||||
create(:grupo_evento_assinatura, grupo_contato: diretoria, evento_notificacao: evento)
|
||||
create(:grupo_evento_assinatura, grupo_contato: operacao, evento_notificacao: evento)
|
||||
|
||||
expect(evento.reload.destinatarios('whatsapp')).to contain_exactly(a, b)
|
||||
end
|
||||
|
||||
it 'respeita o canal da assinatura' do
|
||||
so_email = create(:contato, grupo_contato: diretoria, telefone: nil, email: 'dir@reem.com')
|
||||
create(:grupo_evento_assinatura, grupo_contato: diretoria, evento_notificacao: evento, canal: 'email')
|
||||
|
||||
expect(evento.reload.destinatarios('email')).to contain_exactly(so_email)
|
||||
expect(evento.reload.destinatarios('whatsapp')).to be_empty
|
||||
end
|
||||
|
||||
it 'ignora assinatura desativada e contato desativado' do
|
||||
create(:contato, grupo_contato: diretoria, ativo: false)
|
||||
create(:grupo_evento_assinatura, grupo_contato: diretoria, evento_notificacao: evento, ativo: false)
|
||||
|
||||
expect(evento.reload.destinatarios('whatsapp')).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#vencido?' do
|
||||
let(:evento) do
|
||||
create(:evento_notificacao, gatilho: 'agendado', frequencia: 'diaria', hora: 8)
|
||||
end
|
||||
|
||||
it 'vence depois da hora e só uma vez por dia' do
|
||||
manha = Time.zone.local(2026, 8, 24, 9, 0)
|
||||
expect(evento.vencido?(manha)).to be(true)
|
||||
|
||||
evento.update!(ultimo_disparo_em: Time.zone.local(2026, 8, 24, 8, 5))
|
||||
expect(evento.vencido?(manha)).to be(false)
|
||||
|
||||
expect(evento.vencido?(Time.zone.local(2026, 8, 25, 9, 0))).to be(true)
|
||||
end
|
||||
|
||||
it 'não vence antes da hora' do
|
||||
expect(evento.vencido?(Time.zone.local(2026, 8, 24, 7, 0))).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
67
spec/models/mensagem_template_spec.rb
Normal file
67
spec/models/mensagem_template_spec.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe MensagemTemplate do
|
||||
let(:evento) { create(:evento_notificacao, gatilho: 'pagamento_efetuado') }
|
||||
|
||||
def montar(blocos, **extra)
|
||||
described_class.new({ evento_notificacao: evento, canal: 'whatsapp', blocos: blocos }.merge(extra))
|
||||
end
|
||||
|
||||
# `blocos` vem de um hidden preenchido por JavaScript — ou seja, de fora.
|
||||
describe 'limpeza dos blocos na gravação' do
|
||||
it 'descarta tipo fora do catálogo' do
|
||||
t = montar([{ 'tipo' => 'iframe', 'texto' => 'x' }, { 'tipo' => 'texto', 'texto' => 'ok' }])
|
||||
t.validate
|
||||
expect(t.blocos.map { |b| b['tipo'] }).to eq(['texto'])
|
||||
end
|
||||
|
||||
it 'descarta campo que o tipo não tem' do
|
||||
t = montar([{ 'tipo' => 'divisor', 'onload' => 'alert(1)' }])
|
||||
t.validate
|
||||
expect(t.blocos.first.keys).to eq(['tipo'])
|
||||
end
|
||||
|
||||
it 'descarta linha de tabela sem rótulo nem valor' do
|
||||
t = montar([{ 'tipo' => 'tabela', 'linhas' => [{ 'rotulo' => '', 'valor' => '' },
|
||||
{ 'rotulo' => 'Valor', 'valor' => '10' }] }])
|
||||
t.validate
|
||||
expect(t.blocos.first['linhas'].size).to eq(1)
|
||||
end
|
||||
|
||||
it 'limita a quantidade de blocos' do
|
||||
t = montar(Array.new(60) { { 'tipo' => 'divisor' } })
|
||||
t.validate
|
||||
expect(t.blocos.size).to eq(described_class::MAX_BLOCOS)
|
||||
end
|
||||
|
||||
it 'aceita blocos com chave símbolo (vindos de código, não do form)' do
|
||||
t = montar([{ tipo: 'texto', texto: 'oi' }])
|
||||
t.validate
|
||||
expect(t.blocos).to eq([{ 'tipo' => 'texto', 'texto' => 'oi' }])
|
||||
end
|
||||
end
|
||||
|
||||
it 'exige assunto no e-mail com conteúdo' do
|
||||
t = montar([{ 'tipo' => 'texto', 'texto' => 'oi' }], canal: 'email', assunto: '')
|
||||
expect(t).not_to be_valid
|
||||
expect(t.errors[:assunto]).to be_present
|
||||
end
|
||||
|
||||
# Template ativo e vazio mandaria mensagem em branco — pior que o texto padrão.
|
||||
it 'template vazio não é utilizável mesmo estando ativo' do
|
||||
expect(montar([], ativo: true).utilizavel?).to be(false)
|
||||
expect(montar([{ 'tipo' => 'texto', 'texto' => 'oi' }], ativo: true).utilizavel?).to be(true)
|
||||
expect(montar([{ 'tipo' => 'texto', 'texto' => 'oi' }], ativo: false).utilizavel?).to be(false)
|
||||
end
|
||||
|
||||
it 'não deixa dois templates do mesmo canal no mesmo evento' do
|
||||
described_class.create!(evento_notificacao: evento, canal: 'whatsapp', blocos: [])
|
||||
duplicado = described_class.new(evento_notificacao: evento, canal: 'whatsapp', blocos: [])
|
||||
expect(duplicado).not_to be_valid
|
||||
end
|
||||
|
||||
it 'preview usa os valores de amostra do gatilho' do
|
||||
t = montar([{ 'tipo' => 'texto', 'texto' => 'Valor: {{valor}}' }])
|
||||
expect(t.preview).to include(Notificacao::Variaveis.amostra('pagamento_efetuado')['valor'])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user