From c56109cda74de954ab2d01e65d8e5f5978921418 Mon Sep 17 00:00:00 2001 From: Brad Buchanan Date: Tue, 29 Oct 2019 10:46:55 -0700 Subject: [PATCH 1/4] Remove Pd::CsfCertificateController Deletes Pd::CsfCertificateController and removes the `/generate_csf_certificate/:code` route that was [deprecated in June of this year][0]. We've been monitoring traffic to this route [via Honeybadger][1] and have only seen five hits in the last four months. That's a volume we are happy to handle through our support desk if needed. [0]: https://github.com/code-dot-org/code-dot-org/pull/28762 [1]: https://app.honeybadger.io/projects/3240/faults/50875900 --- .../pd/csf_certificate_controller.rb | 25 ------------------- dashboard/config/routes.rb | 1 - .../pd/csf_certificate_controller_test.rb | 24 ------------------ 3 files changed, 50 deletions(-) delete mode 100644 dashboard/app/controllers/pd/csf_certificate_controller.rb delete mode 100644 dashboard/test/controllers/pd/csf_certificate_controller_test.rb diff --git a/dashboard/app/controllers/pd/csf_certificate_controller.rb b/dashboard/app/controllers/pd/csf_certificate_controller.rb deleted file mode 100644 index 50268c78faac0..0000000000000 --- a/dashboard/app/controllers/pd/csf_certificate_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'honeybadger/ruby' - -# -# This controller is deprecated as of June 2019. All certificates should be served -# through WorkshopCertificateController now. -# We're not immediately removing this because teachers may have bookmarked their -# certificate link and would expect to get the same certificate, here. -# -class Pd::CsfCertificateController < ApplicationController - def generate_certificate - Honeybadger.notify( - error_class: 'DeprecatedEndpointWarning', - error_message: <<~MESSAGE, - Somebody called GET #{request.path}, which was deprecated in June 2019. - This might be someone visiting a bookmark or browser history, but we should - follow up and see if we have a leftover link to this route somewhere. - See https://github.com/code-dot-org/code-dot-org/pull/28762 for details. - MESSAGE - context: { - referer: request.referer - } - ) - redirect_to controller: 'pd/workshop_certificate', action: 'generate_certificate' - end -end diff --git a/dashboard/config/routes.rb b/dashboard/config/routes.rb index 2d0463a890251..1b6d61e35acdd 100644 --- a/dashboard/config/routes.rb +++ b/dashboard/config/routes.rb @@ -509,7 +509,6 @@ get 'pre_workshop_survey/:enrollment_code', action: 'new', controller: 'pre_workshop_survey', as: 'new_pre_workshop_survey' get 'teachercon_survey/:enrollment_code', action: 'new', controller: 'teachercon_survey', as: 'new_teachercon_survey' - get 'generate_csf_certificate/:enrollment_code', controller: 'csf_certificate', action: 'generate_certificate' get 'generate_workshop_certificate/:enrollment_code', controller: 'workshop_certificate', action: 'generate_certificate' get 'attend/:session_code', controller: 'session_attendance', action: 'attend' diff --git a/dashboard/test/controllers/pd/csf_certificate_controller_test.rb b/dashboard/test/controllers/pd/csf_certificate_controller_test.rb deleted file mode 100644 index c92503a4ccb7d..0000000000000 --- a/dashboard/test/controllers/pd/csf_certificate_controller_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' - -class Pd::CsfCertificateControllerTest < ::ActionController::TestCase - test 'Redirects to generic workshop certificate controller' do - Honeybadger.stubs :notify - enrollment = create :pd_enrollment - get :generate_certificate, params: {enrollment_code: enrollment.code} - assert_redirected_to "/pd/generate_workshop_certificate/#{enrollment.code}" - end - - test 'Sends a Honeybadger DeprecatedEndpointWarning notification' do - test_referer = 'http://example.com/example_referer' - - Honeybadger.expects(:notify).with do |params| - assert_equal 'DeprecatedEndpointWarning', params[:error_class] - assert_includes params[:error_message], '/pd/generate_csf_certificate/abcd' - assert_equal test_referer, params[:context][:referer] - true - end - - @request.env['HTTP_REFERER'] = test_referer - get :generate_certificate, params: {enrollment_code: 'abcd'} - end -end From 537c1b95c6bc307d4822401844ed0474b4f6c376 Mon Sep 17 00:00:00 2001 From: Erin Bond Date: Tue, 29 Oct 2019 12:41:20 -0700 Subject: [PATCH 2/4] hide back to school announcement from teacher homepage --- apps/src/templates/studioHomepages/TeacherHomepage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/templates/studioHomepages/TeacherHomepage.jsx b/apps/src/templates/studioHomepages/TeacherHomepage.jsx index 5b47481316fd4..da8accd5fc3b8 100644 --- a/apps/src/templates/studioHomepages/TeacherHomepage.jsx +++ b/apps/src/templates/studioHomepages/TeacherHomepage.jsx @@ -175,7 +175,7 @@ export default class TeacherHomepage extends Component { const showSpecialAnnouncement = false; // Hide the regular announcement/notification for now. - const showAnnouncement = true; + const showAnnouncement = false; return (
@@ -194,7 +194,7 @@ export default class TeacherHomepage extends Component { type={announcement.type || 'bullhorn'} notice={announcement.heading} details={announcement.description} - dismissible={false} + dismissible={true} buttonText={announcement.buttonText} buttonLink={announcement.link} newWindow={true} From ebe5bb6ee42de8e83e7395a9332e811050f8c082 Mon Sep 17 00:00:00 2001 From: Elijah Hamovitz Date: Tue, 29 Oct 2019 12:52:40 -0700 Subject: [PATCH 3/4] Add an eyes test for assessment levels Specifically, one which would have caught the bug reverted in https://github.com/code-dot-org/code-dot-org/pull/31544 --- dashboard/test/ui/features/initial_page_views2.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/test/ui/features/initial_page_views2.feature b/dashboard/test/ui/features/initial_page_views2.feature index 9ec5a98a5c226..9d20a463c54b7 100644 --- a/dashboard/test/ui/features/initial_page_views2.feature +++ b/dashboard/test/ui/features/initial_page_views2.feature @@ -24,4 +24,5 @@ Feature: Looking at a few things with Applitools Eyes - Part 2 | http://studio.code.org/s/allthethings | logged in script progress | css | | http://studio.code.org/s/course4/stage/1/puzzle/1 | unplugged video level | css | | http://studio.code.org/s/allthethings/stage/18/puzzle/14 | embed video | css | + | http://studio.code.org/s/allthethings/stage/26/puzzle/1 | rich long assessment | css | | http://studio.code.org/s/allthethings/stage/27/puzzle/1 | free response | css | From 5ed2e6092e58b0a1574bfa530158547c1ac06ff5 Mon Sep 17 00:00:00 2001 From: Elijah Hamovitz Date: Tue, 29 Oct 2019 14:18:15 -0700 Subject: [PATCH 4/4] Revert "Extract email sending logic from deliver_poste_messages cron script into lib/cdo/poste.rb" --- bin/cron/deliver_poste_messages | 240 ++++++++++++++++++++++++++++++++ lib/cdo/poste.rb | 238 ------------------------------- lib/test/test_deliverer.rb | 51 ------- 3 files changed, 240 insertions(+), 289 deletions(-) delete mode 100644 lib/test/test_deliverer.rb diff --git a/bin/cron/deliver_poste_messages b/bin/cron/deliver_poste_messages index 04a28edcf8ba5..5b3e00d4e376c 100755 --- a/bin/cron/deliver_poste_messages +++ b/bin/cron/deliver_poste_messages @@ -2,6 +2,7 @@ require File.expand_path('../../../pegasus/src/env', __FILE__) require 'retryable' require 'cdo/only_one' +require 'cdo/parse_email_address_string' require 'cdo/poste' require 'honeybadger/ruby' require 'base64' @@ -17,6 +18,20 @@ BATCH_SIZE = 500_000 MAX_THREAD_COUNT = 50 MIN_MESSAGES_PER_THREAD = 50 +# Attempt SMTP connections up to 5 times, retrying on the following error types AND message match. +CONNECTION_ATTEMPTS = 5 +RETRYABLE_ERROR_TYPES = [ + Net::SMTPServerBusy, + Net::SMTPAuthenticationError, + EOFError +].freeze +RETRYABLE_ERROR_MESSAGES = [ + 'Too many connections, try again later', + 'Temporary authentication failure', + 'end of file reached' +].map(&:freeze).freeze +RETRYABLE_ERROR_MESSAGE_MATCH = Regexp.new RETRYABLE_ERROR_MESSAGES.map {|m| "(#{m})"}.join('|') + SMTP_OPTIONS = { address: CDO.poste_smtp_server, port: 587, @@ -32,6 +47,231 @@ SMTP_OPTIONS = { # domain:'code.org', #} +MESSAGE_TEMPLATES = {}.tap do |results| + POSTE_DB[:poste_messages].all.each do |message| + results[message[:id]] = message + end +end + +POSTE_BASE_URL = (rack_env?(:production) ? 'https://' : 'http://') + CDO.poste_host +def poste_url(*parts) + File.join(POSTE_BASE_URL, *parts) +end + +module Poste + class Template + def initialize(body, engine=TextRender::MarkdownEngine) + if match = body.match(/^---\s*\n(?
.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?\z)/m) + @header = TextRender::YamlEngine.new(match[:header].strip) + @html = engine.new(match[:html].strip) + @text = TextRender::ErbEngine.new(match[:text].strip) + elsif match = body.match(/^---\s*\n(?
.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?\z)/m) + @header = TextRender::YamlEngine.new(match[:header].strip) + @html = engine.new(match[:html].strip) + else + @html = engine.new(body.strip) + end + end + + def render(params={}) + if params.key?('form_id') + form = Form2.from_row(DB[:forms].where(id: params['form_id']).first) + params.merge! form.data + params.merge! form.processed_data + params['form'] = form + end + locals = OpenStruct.new(params).instance_eval {binding} + + header = @header.result(locals) unless @header.nil? + # TODO(andrew): Fix this so that we get a signal as to how often this is happening. + # For more information, see https://www.pivotaltracker.com/story/show/104750788. + tracking_id = header['litmus_tracking_id'] unless header.nil? + + html = @html.result(locals) if @html + # Parse the html into a DOM and then re-serialize back to html text in case we were depending on that + # logic in the click tracking method to clean up or canonicalize the HTML. + html = Nokogiri::HTML(html).to_html if html + html = inject_litmus_tracking html, tracking_id, params[:encrypted_id] if html + text = @text.result(locals) unless @text.nil? + + [header, html, text] + end + + def inject_litmus_tracking(html, tracking_id, unique_id) + return html unless tracking_id && unique_id + litmus_blob = <<-eos +
+ +eos + html.gsub("", litmus_blob + "\n") + end + end +end + +class Deliverer + def initialize(params) + @params = params.dup + @smtp = reset_connection + @templates = {} + end + + def reset_connection + @smtp.finish if @smtp + @smtp = smtp_connect unless rack_env?(:development) + end + + def send(delivery) + recipient = POSTE_DB[:contacts].where(id: delivery[:contact_id]).first + message = MESSAGE_TEMPLATES[delivery[:message_id]] + encrypted_id = Poste.encrypt_id(delivery[:id]) + params = JSON.parse(delivery[:params]) + unsubscribe_url = poste_url("/u/#{CGI.escape(encrypted_id)}") + + header, html, _ = load_template(message[:name]).render( + params.merge( + { + recipient: OpenStruct.new(recipient), + encrypted_id: encrypted_id, + unsubscribe_link: unsubscribe_url, + tracking_pixel: poste_url("/o/#{encrypted_id}"), + } + ) + ) + + message = StringIO.new + + # Merge contact_email from the delivery for code studio students whose emails we don't store in contacts. + to_address = parse_address(header['to'], recipient.merge({temporary_email: delivery[:contact_email]})) + message.puts 'To: ' + format_address(to_address) + + from_address = parse_address(header['from'], {email: 'help@code.org', name: 'Code.org'}) + message.puts 'From: ' + format_address(from_address) + + # List of the email part of all destination addresses, including To, Cc, and Bcc + # Note if any of these are omitted it won't be delivered to them even though they still appear in the headers. + # See https://ruby-doc.org/stdlib-2.0.0/libdoc/net/smtp/rdoc/Net/SMTP.html#method-i-send_message + # and https://stackoverflow.com/questions/2530142/ruby-netsmtp-send-email-with-bcc-recipients + to_addresses = [to_address[:email]] + ['Cc', 'Bcc'].each do |field| + next unless address = parse_address(header[field.downcase]) + message.puts "#{field}: #{format_address(address)}" + to_addresses << address[:email] + end + + ['Reply-To', 'Sender'].each do |field| + next unless address = parse_address(header[field.downcase]) + message.puts "#{field}: #{format_address(address)}" + end + + subject = header['subject'].to_s.strip + message.puts 'Subject: ' + subject unless subject.empty? + + message.puts "X-Unsubscribe-Web: #{unsubscribe_url}" + message.puts "List-Unsubscribe: <#{unsubscribe_url}>" + + message.puts 'MIME-Version: 1.0' + + attachments = header['attachments'] || {} + if params['attachments'] + attached_files = Poste2.load_attachments(params['attachments']) + attachments.merge! attached_files + end + + marker = "==_mimepart_#{SecureRandom.hex(17)}" + message.puts "Content-Type: multipart/mixed; boundary=\"#{marker}\"" + + message.puts '' + message.puts "--#{marker}" + + message.puts 'Content-Type: text/html; charset=UTF-8' + message.puts 'Content-Transfer-Encoding: 8bit' + message.puts '' + message.write html + + unless attachments.empty? + attachments.each_pair do |filename, content| + message.puts '' + message.puts "--#{marker}" + message.puts "Content-Type: image/jpeg; charset=UTF-8; filename=\"#{filename}\"" + message.puts 'Content-Transfer-Encoding: base64' + message.puts "Content-Disposition: attachment; filename=\"#{filename}\"" + message.puts '' + + message.write content.scan(/.{1,61}/).join("\n") + end + end + + message.puts '' + message.puts "--#{marker}--" + + if !rack_env?(:development) + @smtp.send_message message.string, from_address[:email], *to_addresses + else + puts(message.string) + end + end + + private + + def format_address(address) + email = address[:email].to_s.strip + raise ArgumentError, 'No :email' if email.empty? + + name = address[:name].to_s.strip + return email if name.empty? + + name = "\"#{name.tr('"', '\"').tr("'", "\'")}\"" if name =~ /[;,\"\'\(\)]/ + "#{name} <#{email}>".strip + end + + def load_template(name) + template = @templates[name] + return template if template + + path = Poste.resolve_template(name) + raise ArgumentError, "[Poste] '#{name}' template wasn't found." unless path + + engine = { + '.haml' => TextRender::HamlEngine, + '.html' => TextRender::ErbEngine, + '.md' => TextRender::MarkdownEngine, + '.txt' => TextRender::MarkdownEngine, + '.yml' => TextRender::YamlEngine, + }[File.extname(path).downcase] + + @templates[name] = Poste::Template.new IO.read(path), engine + end + + def parse_address(address, defaults={}) + address = address.to_s.strip + return parse_email_address_string(address) unless address.empty? + + # Student accounts don't have a stored email in contacts, + # so we use the temporary email here when email doesn't exist. + email = defaults[:email].to_s.strip + email = defaults[:temporary_email] if email.blank? + return nil if email.blank? + + {email: email}.tap do |name_and_email| + name = defaults[:name].to_s.strip + name_and_email[:name] = name unless name.empty? + end + end + + def smtp_connect + Retryable.retryable( + tries: CONNECTION_ATTEMPTS, + on: RETRYABLE_ERROR_TYPES, + matching: RETRYABLE_ERROR_MESSAGE_MATCH + ) do + Net::SMTP.new(@params[:address], @params[:port]).tap do |smtp| + smtp.enable_starttls if @params[:enable_starttls_auto] + smtp.start(@params[:domain], @params[:user_name], @params[:password], @params[:authentication]) + end + end + end +end + def create_threads(count) [].tap do |threads| count.times do diff --git a/lib/cdo/poste.rb b/lib/cdo/poste.rb index 7c618c51c355b..df9113a203df7 100644 --- a/lib/cdo/poste.rb +++ b/lib/cdo/poste.rb @@ -1,7 +1,5 @@ require 'base64' require 'cdo/db' -require 'cdo/parse_email_address_string' -require 'cdo/pegasus/text_render' require 'digest/md5' require_relative 'email_validator' require 'mail' @@ -107,242 +105,6 @@ def self.unsubscribe(email, hashed_email, params={}) ) end end - - class Template - def initialize(body, engine=TextRender::MarkdownEngine) - if match = body.match(/^---\s*\n(?
.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?\z)/m) - @header = TextRender::YamlEngine.new(match[:header].strip) - @html = engine.new(match[:html].strip) - @text = TextRender::ErbEngine.new(match[:text].strip) - elsif match = body.match(/^---\s*\n(?
.*?\n?)^(---\s*$\n?)(?\s*\n.*?\n?\z)/m) - @header = TextRender::YamlEngine.new(match[:header].strip) - @html = engine.new(match[:html].strip) - else - @html = engine.new(body.strip) - end - end - - def render(params={}) - if params.key?('form_id') - form = Form2.from_row(DB[:forms].where(id: params['form_id']).first) - params.merge! form.data - params.merge! form.processed_data - params['form'] = form - end - locals = OpenStruct.new(params).instance_eval {binding} - - header = @header.result(locals) unless @header.nil? - # TODO(andrew): Fix this so that we get a signal as to how often this is happening. - # For more information, see https://www.pivotaltracker.com/story/show/104750788. - tracking_id = header['litmus_tracking_id'] unless header.nil? - - html = @html.result(locals) if @html - # Parse the html into a DOM and then re-serialize back to html text in case we were depending on that - # logic in the click tracking method to clean up or canonicalize the HTML. - html = Nokogiri::HTML(html).to_html if html - html = inject_litmus_tracking html, tracking_id, params[:encrypted_id] if html - text = @text.result(locals) unless @text.nil? - - [header, html, text] - end - - def inject_litmus_tracking(html, tracking_id, unique_id) - return html unless tracking_id && unique_id - litmus_blob = <<-eos -
- -eos - html.gsub("", litmus_blob + "\n") - end - end -end - -class Deliverer - def initialize(params) - @params = params.dup - @smtp = reset_connection - @templates = {} - end - - def reset_connection - @smtp.finish if @smtp - @smtp = smtp_connect unless rack_env?(:development) - end - - POSTE_BASE_URL = (rack_env?(:production) ? 'https://' : 'http://') + CDO.poste_host - def poste_url(*parts) - File.join(POSTE_BASE_URL, *parts) - end - - # lazily-populate this constant so we aren't trying to make database queries - # whenever this file gets required, just once it starts to get used. - MESSAGE_TEMPLATES = Hash.new do |h, key| - h[key] = POSTE_DB[:poste_messages].where(id: key).first - end - - def send(delivery) - recipient = POSTE_DB[:contacts].where(id: delivery[:contact_id]).first - message = MESSAGE_TEMPLATES[delivery[:message_id]] - encrypted_id = Poste.encrypt_id(delivery[:id]) - params = JSON.parse(delivery[:params]) - unsubscribe_url = poste_url("/u/#{CGI.escape(encrypted_id)}") - - header, html, _ = load_template(message[:name]).render( - params.merge( - { - recipient: OpenStruct.new(recipient), - encrypted_id: encrypted_id, - unsubscribe_link: unsubscribe_url, - tracking_pixel: poste_url("/o/#{encrypted_id}"), - } - ) - ) - - message = StringIO.new - - # Merge contact_email from the delivery for code studio students whose emails we don't store in contacts. - to_address = parse_address(header['to'], recipient.merge({temporary_email: delivery[:contact_email]})) - message.puts 'To: ' + format_address(to_address) - - from_address = parse_address(header['from'], {email: 'help@code.org', name: 'Code.org'}) - message.puts 'From: ' + format_address(from_address) - - # List of the email part of all destination addresses, including To, Cc, and Bcc - # Note if any of these are omitted it won't be delivered to them even though they still appear in the headers. - # See https://ruby-doc.org/stdlib-2.0.0/libdoc/net/smtp/rdoc/Net/SMTP.html#method-i-send_message - # and https://stackoverflow.com/questions/2530142/ruby-netsmtp-send-email-with-bcc-recipients - to_addresses = [to_address[:email]] - ['Cc', 'Bcc'].each do |field| - next unless address = parse_address(header[field.downcase]) - message.puts "#{field}: #{format_address(address)}" - to_addresses << address[:email] - end - - ['Reply-To', 'Sender'].each do |field| - next unless address = parse_address(header[field.downcase]) - message.puts "#{field}: #{format_address(address)}" - end - - subject = header['subject'].to_s.strip - message.puts 'Subject: ' + subject unless subject.empty? - - message.puts "X-Unsubscribe-Web: #{unsubscribe_url}" - message.puts "List-Unsubscribe: <#{unsubscribe_url}>" - - message.puts 'MIME-Version: 1.0' - - attachments = header['attachments'] || {} - if params['attachments'] - attached_files = Poste2.load_attachments(params['attachments']) - attachments.merge! attached_files - end - - marker = "==_mimepart_#{SecureRandom.hex(17)}" - message.puts "Content-Type: multipart/mixed; boundary=\"#{marker}\"" - - message.puts '' - message.puts "--#{marker}" - - message.puts 'Content-Type: text/html; charset=UTF-8' - message.puts 'Content-Transfer-Encoding: 8bit' - message.puts '' - message.write html - - unless attachments.empty? - attachments.each_pair do |filename, content| - message.puts '' - message.puts "--#{marker}" - message.puts "Content-Type: image/jpeg; charset=UTF-8; filename=\"#{filename}\"" - message.puts 'Content-Transfer-Encoding: base64' - message.puts "Content-Disposition: attachment; filename=\"#{filename}\"" - message.puts '' - - message.write content.scan(/.{1,61}/).join("\n") - end - end - - message.puts '' - message.puts "--#{marker}--" - - if !rack_env?(:development) - @smtp.send_message message.string, from_address[:email], *to_addresses - else - puts(message.string) - end - end - - def load_template(name) - template = @templates[name] - return template if template - - path = Poste.resolve_template(name) - raise ArgumentError, "[Poste] '#{name}' template wasn't found." unless path - - engine = { - '.haml' => TextRender::HamlEngine, - '.html' => TextRender::ErbEngine, - '.md' => TextRender::MarkdownEngine, - '.txt' => TextRender::MarkdownEngine, - '.yml' => TextRender::YamlEngine, - }[File.extname(path).downcase] - - @templates[name] = Poste::Template.new IO.read(path), engine - end - - private - - def format_address(address) - email = address[:email].to_s.strip - raise ArgumentError, 'No :email' if email.empty? - - name = address[:name].to_s.strip - return email if name.empty? - - name = "\"#{name.tr('"', '\"').tr("'", "\'")}\"" if name =~ /[;,\"\'\(\)]/ - "#{name} <#{email}>".strip - end - - def parse_address(address, defaults={}) - address = address.to_s.strip - return parse_email_address_string(address) unless address.empty? - - # Student accounts don't have a stored email in contacts, - # so we use the temporary email here when email doesn't exist. - email = defaults[:email].to_s.strip - email = defaults[:temporary_email] if email.blank? - return nil if email.blank? - - {email: email}.tap do |name_and_email| - name = defaults[:name].to_s.strip - name_and_email[:name] = name unless name.empty? - end - end - - # Attempt SMTP connections up to 5 times, retrying on the following error types AND message match. - CONNECTION_ATTEMPTS = 5 - RETRYABLE_ERROR_TYPES = [ - Net::SMTPServerBusy, - Net::SMTPAuthenticationError, - EOFError - ].freeze - RETRYABLE_ERROR_MESSAGES = [ - 'Too many connections, try again later', - 'Temporary authentication failure', - 'end of file reached' - ].map(&:freeze).freeze - RETRYABLE_ERROR_MESSAGE_MATCH = Regexp.new RETRYABLE_ERROR_MESSAGES.map {|m| "(#{m})"}.join('|') - def smtp_connect - Retryable.retryable( - tries: CONNECTION_ATTEMPTS, - on: RETRYABLE_ERROR_TYPES, - matching: RETRYABLE_ERROR_MESSAGE_MATCH - ) do - Net::SMTP.new(@params[:address], @params[:port]).tap do |smtp| - smtp.enable_starttls if @params[:enable_starttls_auto] - smtp.start(@params[:domain], @params[:user_name], @params[:password], @params[:authentication]) - end - end - end end module Poste2 diff --git a/lib/test/test_deliverer.rb b/lib/test/test_deliverer.rb deleted file mode 100644 index c18206decb566..0000000000000 --- a/lib/test/test_deliverer.rb +++ /dev/null @@ -1,51 +0,0 @@ -require_relative 'test_helper' -require 'cdo/poste' - -class FakeSmtp - # make these instance variables easily accessible, so we can make assertions - # about the values passed to send_message - attr_reader :message, :from_address, :to_addresses - - def send_message(message, from_address, *to_addresses) - @message = message - @from_address = from_address - @to_addresses = to_addresses - end -end - -class DelivererTest < Minitest::Test - def setup - @fake_smtp = FakeSmtp.new - Deliverer.any_instance.stubs(:reset_connection).returns(@fake_smtp) - @deliverer = Deliverer.new({}) - end - - def test_deliverer_render - template = @deliverer.load_template("dashboard") - header, html, text = template.render - assert_equal "Code.org ", header["from"] - refute_nil html - assert_nil text - end - - def test_deliverer_send - email = "test@deliverer.send" - contact = Poste2.create_recipient(email, {ip_address: '5.6.7.8.'}) - - # Sequel doesn't have a "find or create by", so we implement it manually - message = POSTE_DB[:poste_messages].where(name: "dashboard").first - message_id = message.nil? ? POSTE_DB[:poste_messages].insert({name: "dashboard"}) : message[:id] - - delivery = { - id: 1, - contact_id: contact[:id], - message_id: message_id, - params: {}.to_json - } - @deliverer.send(delivery) - assert_match "To: #{email}", @fake_smtp.message - assert_match "", @fake_smtp.message - assert_equal "noreply@code.org", @fake_smtp.from_address - assert_equal [email], @fake_smtp.to_addresses - end -end