require 'mail' require 'webmock/rspec' module SendGridActionMailer describe DeliveryMethod do def transform_keys(object, &block) SendGridActionMailer::DeliveryMethod.transform_keys(object, &block) end subject(:mailer) do DeliveryMethod.new(api_key: 'key') end class TestClient attr_reader :sent_mail def send(mail) @sent_mail = mail super(mail) end def mail() self end def _(param) return self if param == 'send' raise "Unknown param #{param.inspect}" end def post(request_body:) @sent_mail = request_body OpenStruct.new(status_code: '200') end end describe 'settings' do it 'has correct api_key' do m = DeliveryMethod.new(api_key: 'ABCDEFG') expect(m.settings[:api_key]).to eq('ABCDEFG') end it 'has correct host' do m = DeliveryMethod.new(host: 'example.com') expect(m.settings[:host]).to eq('example.com') end it 'default raise_delivery_errors' do m = DeliveryMethod.new() expect(m.settings[:raise_delivery_errors]).to eq(false) end it 'sets raise_delivery_errors' do m = DeliveryMethod.new(raise_delivery_errors: true) expect(m.settings[:raise_delivery_errors]).to eq(true) end it 'default return_response' do m = DeliveryMethod.new() expect(mailer.settings[:return_response]).to eq(nil) end it 'sets return_response' do m = DeliveryMethod.new(return_response: true) expect(m.settings[:return_response]).to eq(true) end it 'sets perform_deliveries' do m = DeliveryMethod.new(perform_send_request: false) expect(m.settings[:perform_send_request]).to eq(false) end it 'sets http_options' do m = DeliveryMethod.new(http_options: {open_timeout: 40}) expect(m.settings[:http_options]).to eq({open_timeout: 40}) end end describe '#deliver!' do let(:client) { TestClient.new } let(:client_parent) { double(client: client) } let(:mail) do Mail.new( to: 'test@sendgrid.com', from: 'taco@cat.limo', subject: 'Hello, world!', ) end before do stub_request(:any, 'https://api.sendgrid.com/api/mail.send.json') .to_return(body: {message: 'success'}.to_json, status: 200, headers: {'X-TEST' => 'yes'}) allow(SendGrid::Client).to receive(:new).and_return(client) allow(SendGrid::API).to receive(:new).and_return(client_parent) end context 'with dynamic api_key' do let(:default) do Mail.new( to: 'test@sendgrid.com', from: 'taco@cat.limo', subject: 'Hello, world!' ) end let(:mail) do Mail.new( to: 'test@sendgrid.com', from: 'taco@cat.limo', subject: 'Hello, world!', delivery_method_options: { api_key: 'test_key' } ) end it 'sets dynamic api_key, but should revert to default settings api_key' do expect(SendGrid::API).to receive(:new).with(api_key: 'key', http_options: {}) mailer.deliver!(default) expect(SendGrid::API).to receive(:new).with(api_key: 'test_key', http_options: {}) mailer.deliver!(mail) expect(SendGrid::API).to receive(:new).with(api_key: 'key', http_options: {}) mailer.deliver!(default) end end it 'sets to' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com"}]}) end it 'returns mailer itself' do ret = mailer.deliver!(mail) expect(ret).to eq(mailer) end it 'returns api response' do m = DeliveryMethod.new(return_response: true, api_key: 'key') ret = m.deliver!(mail) expect(ret.status_code).to eq('200') end context 'to with a friendly name' do before { mail.to = 'Test SendGrid ' } it 'sets to' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com", "name"=>"Test SendGrid"}]}) end end context 'to with a friendly name (with quotes)' do before { mail.to = '"Test SendGrid" ' } it 'sets to' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com", "name"=>"Test SendGrid"}]}) end end context 'there are ccs' do before { mail.cc = 'burrito@cat.limo' } it 'sets cc' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com"}], "cc"=>[{"email"=>"burrito@cat.limo"}]}) end end context 'there are bccs' do before { mail.bcc = 'nachos@cat.limo' } it 'sets bcc' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com"}], "bcc"=>[{"email"=>"nachos@cat.limo"}]}) end end context 'there are bccs with a friendly name' do before { mail.bcc = 'Taco Cat ' } it 'sets bcc' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com"}], "bcc"=>[{"email"=>"nachos@cat.limo", "name"=>"Taco Cat"}]}) end end context 'there are bccs with a friendly name (with quotes)' do before { mail.bcc = '"Taco Cat" ' } it 'sets bcc' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to include({"to"=>[{"email"=>"test@sendgrid.com"}], "bcc"=>[{"email"=>"nachos@cat.limo", "name"=>"Taco Cat"}]}) end end context 'there is a reply to' do before { mail.reply_to = 'nachos@cat.limo' } it 'sets reply_to' do mailer.deliver!(mail) expect(client.sent_mail['reply_to']).to eq({'email' => 'nachos@cat.limo'}) end end context 'there is a reply to with a friendly name' do before { mail.reply_to = 'Taco Cat ' } it 'sets reply_to' do mailer.deliver!(mail) expect(client.sent_mail['reply_to']).to eq('email' => 'nachos@cat.limo', 'name' => 'Taco Cat') end end context 'from contains a friendly name' do before { mail.from = 'Taco Cat '} it 'sets from' do mailer.deliver!(mail) expect(client.sent_mail['from']).to eq('email' => 'taco@cat.limo', 'name' => 'Taco Cat') end end context 'from contains a friendly name (with quotes)' do before { mail.from = '"Taco Cat" '} it 'sets from' do mailer.deliver!(mail) expect(client.sent_mail['from']).to eq('email' => 'taco@cat.limo', 'name' => 'Taco Cat') end end it 'sets subject' do mailer.deliver!(mail) expect(client.sent_mail['subject']).to eq('Hello, world!') end it 'sets a text/plain body' do mail.content_type = 'text/plain' mail.body = 'I heard you like pineapple.' mailer.deliver!(mail) expect(client.sent_mail['content']).to eq([ { 'type' => 'text/plain', 'value' => 'I heard you like pineapple.' } ]) end it 'sets a text/html body' do mail.content_type = 'text/html' mail.body = 'I heard you like pineapple.' mailer.deliver!(mail) expect(client.sent_mail['content']).to eq([ { 'type' => 'text/html', 'value' => 'I heard you like pineapple.' } ]) end context 'template_id' do before do mail['template_id'] = '1' end it 'sets a template_id' do mailer.deliver!(mail) expect(client.sent_mail['template_id']).to eq('1') end it 'does not set unsubscribe substitutions' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].first).to_not have_key('substitutions') end it 'does not set send a content type' do mailer.deliver!(mail) expect(client.sent_mail['content']).to eq(nil) end it 'does not set send a content type even if body is given' do # This matches the default behavior of ActionMail. body must be # specified and content_type defaults to text/plain. mail.body = 'I heard you like pineapple.' mail.content_type = 'text/plain' mailer.deliver!(mail) expect(client.sent_mail['content']).to eq(nil) end end context 'without dynamic template data or a template id' do it 'sets unsubscribe substitutions' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].first).to have_key('substitutions') substitutions = client.sent_mail['personalizations'].first['substitutions'] expect(substitutions).to eq({ '%asm_group_unsubscribe_raw_url%' => '<%asm_group_unsubscribe_raw_url%>', '%asm_global_unsubscribe_raw_url%' => '<%asm_global_unsubscribe_raw_url%>', '%asm_preferences_raw_url%' => '<%asm_preferences_raw_url%>' }) end end context 'send options' do it 'sets sections' do mail['sections'] = {'%foo%' => 'bar'} mailer.deliver!(mail) expect(client.sent_mail['sections']).to eq({'%foo%' => 'bar'}) end it 'sets headers' do mail['headers'] = {'X-FOO' => 'bar'} mailer.deliver!(mail) expect(client.sent_mail['headers']).to eq({'X-FOO' => 'bar'}) end it 'sets categories' do mail['categories'] = ['foo', 'bar'] mailer.deliver!(mail) expect(client.sent_mail['categories']).to eq(['foo', 'bar']) end it 'sets custom_args' do mail['custom_args'] = {'campaign' => 'welcome'} mailer.deliver!(mail) expect(client.sent_mail['custom_args']).to eq({'campaign' => 'welcome'}) end it 'sets send_at and batch_id' do epoch = Time.now.to_i mail['send_at'] = epoch mail['batch_id'] = 3 mailer.deliver!(mail) expect(client.sent_mail['send_at']).to eq(epoch) expect(client.sent_mail['batch_id']).to eq('3') end it 'sets asm' do asm = {group_id: 99, groups_to_display: [4,5,6,7,8]} mail['asm'] = asm mailer.deliver!(mail) expect(client.sent_mail['asm']).to eq(transform_keys(asm, &:to_s)) end it 'sets ip_pool_name' do mail['ip_pool_name'] = 'marketing' mailer.deliver!(mail) expect(client.sent_mail['ip_pool_name']).to eq('marketing') end it 'should not change values inside custom args' do custom_args = { 'text' => 'line with a => in it' } mail['custom_args'] = custom_args mailer.deliver!(mail) expect(client.sent_mail['custom_args']).to eq('text' => 'line with a => in it') end context 'mail_settings' do it 'sets bcc' do bcc = { bcc: { enable: true, email: 'test@example.com' }} mail['mail_settings'] = bcc mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(transform_keys(bcc, &:to_s)) end it 'sets bypass_list_management' do bypass = { bypass_list_management: { enable: true }} mail['mail_settings'] = bypass mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(transform_keys(bypass, &:to_s)) end it 'sets footer' do footer = {footer: { enable: true, text: 'Footer Text', html: 'Footer Text'}} mail['mail_settings'] = footer mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(transform_keys(footer, &:to_s)) end it 'sets sandbox_mode' do sandbox = {sandbox_mode: { enable: true }} mail['mail_settings'] = sandbox mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(transform_keys(sandbox, &:to_s)) end it 'sets spam_check' do spam_check = {spam_check: { enable: true, threshold: 1, post_to_url: 'https://spamcatcher.sendgrid.com'}} mail['mail_settings'] = spam_check mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(transform_keys(spam_check, &:to_s)) end end context 'tracking_settings' do it 'sets click_tracking' do tracking = { click_tracking: { enable: false, enable_text: false }} mail['tracking_settings'] = tracking.dup mailer.deliver!(mail) expect(client.sent_mail['tracking_settings']).to eq(transform_keys(tracking, &:to_s)) end it 'sets open_tracking' do tracking = { open_tracking: { enable: true, substitution_tag: 'Optional tag to replace with the open image in the body of the message' }} mail['tracking_settings'] = tracking mailer.deliver!(mail) expect(client.sent_mail['tracking_settings']).to eq(transform_keys(tracking, &:to_s)) end it 'sets subscription_tracking' do tracking = { subscription_tracking: { enable: true, text: 'text to insert into the text/plain portion of the message', html: 'html to insert into the text/html portion of the message', substitution_tag: 'Optional tag to replace with the open image in the body of the def message' }} mail['tracking_settings'] = tracking mailer.deliver!(mail) expect(client.sent_mail['tracking_settings']).to eq(transform_keys(tracking, &:to_s)) end it 'sets ganalytics' do tracking = { ganalytics: { enable: true, utm_source: 'some source', utm_medium: 'some medium', utm_term: 'some term', utm_content: 'some content', utm_campaign: 'some campaign' }} mail['tracking_settings'] = tracking mailer.deliver!(mail) expect(client.sent_mail['tracking_settings']).to eq(transform_keys(tracking, &:to_s)) end end context 'dynamic template data' do let(:template_data) do { variable_1: '1', variable_2: '2' } end before { mail['dynamic_template_data'] = template_data } it 'sets dynamic_template_data' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].first['dynamic_template_data']).to eq(template_data) end it 'does not set unsubscribe substitutions' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].first).to_not have_key('substitutions') end context 'containing what looks like hash syntax' do let(:template_data) do { hint: 'Just use => instead of :' } end it 'does not change values inside dynamic template data' do mailer.deliver!(mail) expect( client.sent_mail['personalizations'].first['dynamic_template_data'] ).to eq(template_data) end end end it 'sets dynamic template data and sandbox_mode' do mail['mail_settings'] = {} mailer.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq(nil) end end context 'multipart/alternative' do before do mail.content_type 'multipart/alternative' mail.part do |part| part.text_part = Mail::Part.new do content_type 'text/plain' body 'I heard you like pineapple.' end part.html_part = Mail::Part.new do content_type 'text/html' body 'I heard you like pineapple.' end end end it 'sets the text and html body' do mailer.deliver!(mail) expect(client.sent_mail['content']).to include({ 'type' => 'text/html', 'value' => 'I heard you like pineapple.' }) expect(client.sent_mail['content']).to include({ 'type' => 'text/plain', 'value' => 'I heard you like pineapple.' }) end end context 'multipart/mixed' do before do mail.content_type 'multipart/mixed' mail.part do |part| part.text_part = Mail::Part.new do content_type 'text/plain' body 'I heard you like pineapple.' end part.html_part = Mail::Part.new do content_type 'text/html' body 'I heard you like pineapple.' end end mail.attachments['specs.rb'] = File.read(__FILE__) end it 'sets the text and html body' do mailer.deliver!(mail) expect(client.sent_mail['content']).to include({ 'type' => 'text/html', 'value' => 'I heard you like pineapple.' }) expect(client.sent_mail['content']).to include({ 'type' => 'text/plain', 'value' => 'I heard you like pineapple.' }) end it 'adds the attachment' do expect(mail.attachments.first.read).to include("it 'adds the attachment' do") mailer.deliver!(mail) attachment = client.sent_mail['attachments'].first expect(attachment['filename']).to eq('specs.rb') expect(attachment['type']).to eq('application/x-ruby') end end context 'multipart/related' do before do mail.content_type 'multipart/related' mail.part do |part| part.text_part = Mail::Part.new do content_type 'text/plain' body 'I heard you like pineapple.' end part.html_part = Mail::Part.new do content_type 'text/html' body 'I heard you like pineapple.' end end mail.attachments.inline['specs.rb'] = File.read(__FILE__) end it 'sets the text and html body' do mailer.deliver!(mail) expect(client.sent_mail['content']).to include({ 'type' => 'text/html', 'value' => 'I heard you like pineapple.' }) expect(client.sent_mail['content']).to include({ 'type' => 'text/plain', 'value' => 'I heard you like pineapple.' }) end it 'adds the inline attachment' do expect(mail.attachments.first.read).to include("it 'adds the inline attachment' do") mailer.deliver!(mail) content = client.sent_mail['attachments'].first expect(content['filename']).to eq('specs.rb') expect(content['type']).to eq('application/x-ruby') expect(content['content_id'].class).to eq(String) expect(content['content_id']).to include("@") expect(content['content_id']).not_to include("<") expect(content['content_id']).not_to include(">") end end context 'with personalizations' do let(:personalizations) do [ { 'to' => [ {'email' => 'john1@example.com', 'name' => 'John 1'}, {'email' => 'john2@example.com', 'name' => 'John 2'}, ] }, { 'to' => [ {'email' => 'john3@example.com', 'name' => 'John 3'}, {'email' => 'john4@example.com'} ], 'cc' => [ {'email' => 'cc@example.com'} ], 'bcc' => [ {'email' => 'bcc@example.com'} ], 'substitutions' => { '%fname%' => 'Bob' }, 'subject' => 'personalized subject', 'send_at' => 1443636843, 'custom_args' => { 'user_id' => '343' }, 'headers' => { 'X-Test' => true } } ] end before do mail.to = nil mail.cc = nil mail.bcc = nil mail['personalizations'] = personalizations end it 'sets the provided to address personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].length).to eq(2) expect(client.sent_mail['personalizations'][0]['to']).to eq(personalizations[0]['to']) expect(client.sent_mail['personalizations'][1]['to']).to eq(personalizations[1]['to']) end it 'sets the provided cc address personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('cc') expect(client.sent_mail['personalizations'][1]['cc']).to eq(personalizations[1]['cc']) end it 'sets the provided bcc address personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('bcc') expect(client.sent_mail['personalizations'][1]['bcc']).to eq(personalizations[1]['bcc']) end it 'sets the provided subject personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('subject') expect(client.sent_mail['personalizations'][1]['subject']).to eq(personalizations[1]['subject']) end it 'sets the provided headers personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('headers') expect(client.sent_mail['personalizations'][1]['headers']).to eq(personalizations[1]['headers']) end it 'sets the provided custom_arg personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('custom_args') expect(client.sent_mail['personalizations'][1]['custom_args']).to eq(personalizations[1]['custom_args']) end it 'sets the provided send_at personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('send_at') expect(client.sent_mail['personalizations'][1]['send_at']).to eq(personalizations[1]['send_at']) end it 'sets the provided substitution personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][1]['substitutions']).to include(personalizations[1]['substitutions']) end it 'adds to the unsubscribe link substitutions' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]['substitutions']).to eq({ '%asm_group_unsubscribe_raw_url%' => '<%asm_group_unsubscribe_raw_url%>', '%asm_global_unsubscribe_raw_url%' => '<%asm_global_unsubscribe_raw_url%>', '%asm_preferences_raw_url%' => '<%asm_preferences_raw_url%>' }) expect(client.sent_mail['personalizations'][1]['substitutions']).to include({ '%asm_group_unsubscribe_raw_url%' => '<%asm_group_unsubscribe_raw_url%>', '%asm_global_unsubscribe_raw_url%' => '<%asm_global_unsubscribe_raw_url%>', '%asm_preferences_raw_url%' => '<%asm_preferences_raw_url%>' }) end context 'with symbols used as keys' do let(:personalizations) do [ { to: [ {email: 'sally1@example.com', name: 'Sally 1'}, {email: 'sally2@example.com', name: 'Sally 2'}, ] } ] end it 'still works' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].length).to eq(1) expected_to = personalizations[0][:to].map { |t| transform_keys(t, &:to_s) } expect(client.sent_mail['personalizations'][0]['to']).to eq(expected_to) end end context 'dynamic template data passed into a personalizaiton' do let(:personalization_data) do { 'variable_1' => '1', 'variable_2' => '2' } end let(:personalizations_with_dynamic_data) do personalizations.tap do |p| p[1]['dynamic_template_data'] = personalization_data end end before do mail['personalizations'] = nil mail['personalizations'] = personalizations_with_dynamic_data end it 'sets the provided dynamic template data personalizations' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]).to_not have_key('dynamic_template_data') expect(client.sent_mail['personalizations'][1]['dynamic_template_data']).to eq(personalization_data) end context 'dynamic template data is also set on the mail object' do let(:mail_template_data) do { 'variable_3' => '1', 'variable_4' => '2' } end before { mail['dynamic_template_data'] = mail_template_data.dup } it 'sets dynamic_template_data where not also provided as a personalization' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][0]['dynamic_template_data']).to eq(mail_template_data) end it 'merges the template data with a personalizations dynamic data' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'][1]['dynamic_template_data']).to eq( mail_template_data.merge(personalization_data) ) end end end context 'when to is set on mail object' do before { mail.to = 'test@sendgrid.com' } it 'adds that to address as a separate personalization' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].length).to eq(3) expect(client.sent_mail['personalizations'][0]['to']).to eq(personalizations[0]['to']) expect(client.sent_mail['personalizations'][1]['to']).to eq(personalizations[1]['to']) expect(client.sent_mail['personalizations'][2]['to']).to eq([{"email"=>"test@sendgrid.com"}]) end end context 'when cc is set on mail object' do before { mail.cc = 'test@sendgrid.com' } it 'adds that cc address as a separate personalization' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].length).to eq(3) expect(client.sent_mail['personalizations'][0]['cc']).to eq(personalizations[0]['cc']) expect(client.sent_mail['personalizations'][1]['cc']).to eq(personalizations[1]['cc']) expect(client.sent_mail['personalizations'][2]['cc']).to eq([{"email"=>"test@sendgrid.com"}]) end end context 'when bcc is set on mail object' do before { mail.bcc = 'test@sendgrid.com' } it 'adds that bcc address as a separate personalization' do mailer.deliver!(mail) expect(client.sent_mail['personalizations'].length).to eq(3) expect(client.sent_mail['personalizations'][0]['bcc']).to eq(personalizations[0]['bcc']) expect(client.sent_mail['personalizations'][1]['bcc']).to eq(personalizations[1]['bcc']) expect(client.sent_mail['personalizations'][2]['bcc']).to eq([{"email"=>"test@sendgrid.com"}]) end end context 'when perform_send_request false' do it 'should not send and email and return json body' do m = DeliveryMethod.new(perform_send_request: false, return_response: true, api_key: 'key') response = m.deliver!(mail) expect(response).to respond_to(:to_json) end end context 'when mail_settings are present' do it 'should apply mail_settings to request body' do m = DeliveryMethod.new(api_key: 'key', return_response: true, mail_settings: { sandbox_mode: {enable: true }}) m.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq("sandbox_mode" => {"enable" => true }) end context 'when mail has mail_settings set' do before { mail['mail_settings'] = { spam_check: { enable: true } } } it 'should combine local mail_settings with global settings' do m = DeliveryMethod.new(api_key: 'key', return_response: true, mail_settings: { sandbox_mode: {enable: true }}) m.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq( "sandbox_mode" => {"enable" => true }, "spam_check" => {"enable" => true }, ) end end context 'when mail contains the same setting as global settings' do before do mail['mail_settings'] = { sandbox_mode: { enable: false }, spam_check: { enable: true } } end it 'should apply local mail_settings on top of global settings' do m = DeliveryMethod.new(api_key: 'key', return_response: true, mail_settings: { sandbox_mode: {enable: true }}) m.deliver!(mail) expect(client.sent_mail['mail_settings']).to eq( "sandbox_mode" => {"enable" => false }, "spam_check" => {"enable" => true }, ) end end end end end end end