courrier 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +16 -24
- data/README.md +178 -74
- data/courrier.gemspec +4 -4
- data/lib/courrier/configuration.rb +2 -4
- data/lib/courrier/email/provider.rb +14 -6
- data/lib/courrier/email/providers/base.rb +7 -2
- data/lib/courrier/email/providers/cloudflare.rb +35 -0
- data/lib/courrier/email/providers/lettermint.rb +31 -0
- data/lib/courrier/email/providers/loops.rb +1 -1
- data/lib/courrier/email/providers/mailgun.rb +1 -1
- data/lib/courrier/email/providers/mailjet.rb +1 -1
- data/lib/courrier/email/providers/mailpace.rb +1 -1
- data/lib/courrier/email/providers/postmark.rb +1 -1
- data/lib/courrier/email/providers/resend.rb +1 -1
- data/lib/courrier/email/providers/sendgrid.rb +1 -1
- data/lib/courrier/email/providers/ses.rb +75 -0
- data/lib/courrier/email/providers/smtp2go.rb +29 -0
- data/lib/courrier/email/providers/sparkpost.rb +1 -1
- data/lib/courrier/email/providers/userlist.rb +1 -1
- data/lib/courrier/email/request.rb +1 -1
- data/lib/courrier/email/transformer.rb +9 -9
- data/lib/courrier/email.rb +50 -38
- data/lib/courrier/errors.rb +0 -2
- data/lib/courrier/markdown.rb +52 -0
- data/lib/courrier/test.rb +38 -0
- data/lib/courrier/test_helper.rb +65 -0
- data/lib/courrier/version.rb +1 -1
- data/lib/courrier.rb +2 -2
- metadata +18 -31
- data/app/controllers/courrier/previews/cleanups_controller.rb +0 -13
- data/app/controllers/courrier/previews_controller.rb +0 -23
- data/app/views/courrier/previews/index.html.erb +0 -171
- data/config/routes.rb +0 -8
- data/lib/courrier/configuration/inbox.rb +0 -21
- data/lib/courrier/email/providers/inbox/default.html.erb +0 -126
- data/lib/courrier/email/providers/inbox.rb +0 -83
- data/lib/courrier/engine.rb +0 -7
- data/lib/courrier/jobs/email_delivery_job.rb +0 -23
- data/lib/courrier/railtie.rb +0 -23
- data/lib/courrier/tasks/courrier.rake +0 -13
- data/lib/generators/courrier/email_generator.rb +0 -42
- data/lib/generators/courrier/install_generator.rb +0 -11
- data/lib/generators/courrier/templates/email/password_reset.rb.tt +0 -29
- data/lib/generators/courrier/templates/email/welcome.rb.tt +0 -43
- data/lib/generators/courrier/templates/email.rb.tt +0 -15
- data/lib/generators/courrier/templates/initializer.rb.tt +0 -43
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Cloudflare < Base
|
|
7
|
+
def body
|
|
8
|
+
{
|
|
9
|
+
"from" => @options.from,
|
|
10
|
+
"to" => @options.to,
|
|
11
|
+
"reply_to" => @options.reply_to,
|
|
12
|
+
"cc" => @options.cc,
|
|
13
|
+
"bcc" => @options.bcc,
|
|
14
|
+
"subject" => @options.subject,
|
|
15
|
+
"text" => @options.text,
|
|
16
|
+
"html" => @options.html
|
|
17
|
+
}.compact
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def endpoint_url
|
|
23
|
+
account_id = @provider_options.account_id
|
|
24
|
+
raise Courrier::ArgumentError, "Cloudflare requires an `account_id`" unless account_id
|
|
25
|
+
|
|
26
|
+
"https://api.cloudflare.com/client/v4/accounts/#{account_id}/email/sending/send"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default_headers
|
|
30
|
+
{"Authorization" => "Bearer #{@api_key}"}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Lettermint < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.lettermint.co/v1/send"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"route" => @provider_options.route,
|
|
12
|
+
"from" => @options.from,
|
|
13
|
+
"to" => @options.to.to_s.split(",").map(&:strip),
|
|
14
|
+
"cc" => @options.cc&.split(",")&.map(&:strip),
|
|
15
|
+
"bcc" => @options.bcc&.split(",")&.map(&:strip),
|
|
16
|
+
"reply_to" => @options.reply_to&.split(",")&.map(&:strip),
|
|
17
|
+
"subject" => @options.subject,
|
|
18
|
+
"html" => @options.html,
|
|
19
|
+
"text" => @options.text
|
|
20
|
+
}.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def default_headers
|
|
26
|
+
{"x-lettermint-token" => @api_key}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Ses < Base
|
|
7
|
+
def deliver
|
|
8
|
+
uri = URI.parse("https://email.#{@provider_options[:region]}.amazonaws.com/v2/email/outbound-emails")
|
|
9
|
+
|
|
10
|
+
request = Net::HTTP::Post.new(uri)
|
|
11
|
+
default_headers.merge(@custom_headers).each { |name, value| request[name] = value }
|
|
12
|
+
|
|
13
|
+
sign!(request)
|
|
14
|
+
|
|
15
|
+
request.body = JSON.dump(body)
|
|
16
|
+
|
|
17
|
+
options = {use_ssl: uri.scheme == "https"}
|
|
18
|
+
response = Net::HTTP.start(uri.hostname, uri.port, options) { it.request(request) }
|
|
19
|
+
|
|
20
|
+
Courrier::Email::Result.new(response: response)
|
|
21
|
+
rescue => error
|
|
22
|
+
Courrier::Email::Result.new(error: error)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def body
|
|
26
|
+
{
|
|
27
|
+
"FromEmailAddress" => @options.from,
|
|
28
|
+
"Destination" => {
|
|
29
|
+
"ToAddresses" => Array(@options.to),
|
|
30
|
+
"CcAddresses" => @options.cc ? Array(@options.cc) : nil,
|
|
31
|
+
"BccAddresses" => @options.bcc ? Array(@options.bcc) : nil
|
|
32
|
+
}.compact,
|
|
33
|
+
|
|
34
|
+
"ReplyToAddresses" => @options.reply_to ? Array(@options.reply_to) : nil,
|
|
35
|
+
"Content" => {
|
|
36
|
+
"Simple" => {
|
|
37
|
+
"Subject" => {"Data" => @options.subject},
|
|
38
|
+
|
|
39
|
+
"Body" => {
|
|
40
|
+
"Text" => {"Data" => @options.text},
|
|
41
|
+
"Html" => {"Data" => @options.html}
|
|
42
|
+
}.compact
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def default_headers
|
|
51
|
+
{
|
|
52
|
+
"Content-Type" => "application/json",
|
|
53
|
+
"Accept" => "application/json"
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sign!(request)
|
|
58
|
+
require "aws-sigv4"
|
|
59
|
+
|
|
60
|
+
Aws::Sigv4::Signer.new(
|
|
61
|
+
service: "ses",
|
|
62
|
+
region: @provider_options[:region],
|
|
63
|
+
access_key_id: @provider_options[:access_key_id],
|
|
64
|
+
secret_access_key: @provider_options[:secret_access_key],
|
|
65
|
+
session_token: @provider_options[:session_token]
|
|
66
|
+
).sign_request(
|
|
67
|
+
http_method: request.method,
|
|
68
|
+
url: request.uri.to_s,
|
|
69
|
+
headers: request.to_hash
|
|
70
|
+
).headers.each { |name, value| request[name] = value }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Providers
|
|
6
|
+
class Smtp2go < Base
|
|
7
|
+
ENDPOINT_URL = "https://api.smtp2go.com/v3/email/send"
|
|
8
|
+
|
|
9
|
+
def body
|
|
10
|
+
{
|
|
11
|
+
"sender" => @options.from,
|
|
12
|
+
"to" => @options.to.to_s.split(",").map(&:strip),
|
|
13
|
+
"cc" => @options.cc&.split(",")&.map(&:strip),
|
|
14
|
+
"bcc" => @options.bcc&.split(",")&.map(&:strip),
|
|
15
|
+
"subject" => @options.subject,
|
|
16
|
+
"html_body" => @options.html,
|
|
17
|
+
"text_body" => @options.text
|
|
18
|
+
}.compact
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def default_headers
|
|
24
|
+
{"X-Smtp2go-Api-Key" => @api_key}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -25,7 +25,7 @@ module Courrier
|
|
|
25
25
|
options = {use_ssl: uri.scheme == "https"}
|
|
26
26
|
|
|
27
27
|
begin
|
|
28
|
-
response = Net::HTTP.start(uri.hostname, uri.port, options) {
|
|
28
|
+
response = Net::HTTP.start(uri.hostname, uri.port, options) { it.request(request) }
|
|
29
29
|
|
|
30
30
|
Result.new(response: response)
|
|
31
31
|
rescue => error
|
|
@@ -11,30 +11,30 @@ module Courrier
|
|
|
11
11
|
|
|
12
12
|
def to_text
|
|
13
13
|
Nokogiri::HTML(@content)
|
|
14
|
-
.then { remove_unwanted_elements(
|
|
15
|
-
.then { process_links(
|
|
16
|
-
.then { preserve_line_breaks(
|
|
17
|
-
.then { clean_up(
|
|
14
|
+
.then { remove_unwanted_elements(it) }
|
|
15
|
+
.then { process_links(it) }
|
|
16
|
+
.then { preserve_line_breaks(it) }
|
|
17
|
+
.then { clean_up(it) }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
BLOCK_ELEMENTS = %w[p div h1 h2 h3 h4 h5 h6 pre blockquote li]
|
|
23
23
|
|
|
24
|
-
def remove_unwanted_elements(html) = html.tap {
|
|
24
|
+
def remove_unwanted_elements(html) = html.tap { it.css("script, style").remove }
|
|
25
25
|
|
|
26
26
|
def process_links(html)
|
|
27
27
|
html.tap do |document|
|
|
28
28
|
document.css("a")
|
|
29
|
-
.select { valid?(
|
|
30
|
-
.reject {
|
|
31
|
-
.each {
|
|
29
|
+
.select { valid?(it) }
|
|
30
|
+
.reject { it.text.strip.empty? || it.text.strip == it["href"] }
|
|
31
|
+
.each { it.content = "#{it.text.strip} (#{it["href"]})" }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def preserve_line_breaks(html)
|
|
36
36
|
html.tap do |document|
|
|
37
|
-
document.css(BLOCK_ELEMENTS.join(",")).each {
|
|
37
|
+
document.css(BLOCK_ELEMENTS.join(",")).each { it.after("\n") }
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
data/lib/courrier/email.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
require "erb"
|
|
4
4
|
|
|
5
5
|
require "courrier/email/address"
|
|
6
|
-
require "courrier/jobs/email_delivery_job" if defined?(Rails)
|
|
7
6
|
require "courrier/email/layouts"
|
|
7
|
+
require "courrier/markdown"
|
|
8
8
|
require "courrier/email/options"
|
|
9
9
|
require "courrier/email/provider"
|
|
10
10
|
|
|
@@ -36,7 +36,11 @@ module Courrier
|
|
|
36
36
|
@queue_options ||= {}
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
attr_writer :queue_options
|
|
39
|
+
attr_writer :queue_options, :headers
|
|
40
|
+
|
|
41
|
+
def headers(**options)
|
|
42
|
+
options.empty? ? (@headers ||= {}) : @headers = options
|
|
43
|
+
end
|
|
40
44
|
|
|
41
45
|
def enqueue(**options)
|
|
42
46
|
self.queue_options = options
|
|
@@ -47,23 +51,29 @@ module Courrier
|
|
|
47
51
|
self.layouts = options
|
|
48
52
|
end
|
|
49
53
|
|
|
54
|
+
def before_deliver(&block)
|
|
55
|
+
(@before_deliver ||= []) << block
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def after_deliver(&block)
|
|
59
|
+
(@after_deliver ||= []) << block
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def before_deliver_callbacks
|
|
63
|
+
@before_deliver || []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def after_deliver_callbacks
|
|
67
|
+
@after_deliver || []
|
|
68
|
+
end
|
|
69
|
+
|
|
50
70
|
def deliver(**options)
|
|
51
71
|
new(options).deliver_now
|
|
52
72
|
end
|
|
53
73
|
alias_method :deliver_now, :deliver
|
|
54
74
|
|
|
55
|
-
def deliver_later(**options)
|
|
56
|
-
new(options).deliver_later
|
|
57
|
-
end
|
|
58
|
-
|
|
59
75
|
def inherited(subclass)
|
|
60
76
|
super
|
|
61
|
-
|
|
62
|
-
# If you read this and know how to move this Rails-specific logic somewhere
|
|
63
|
-
# else, e.g. `lib/courrier/railtie.rb`, open a PR ❤️
|
|
64
|
-
if defined?(Rails) && Rails.application
|
|
65
|
-
subclass.include Rails.application.routes.url_helpers
|
|
66
|
-
end
|
|
67
77
|
end
|
|
68
78
|
end
|
|
69
79
|
|
|
@@ -95,39 +105,22 @@ module Courrier
|
|
|
95
105
|
return nil
|
|
96
106
|
end
|
|
97
107
|
|
|
108
|
+
return nil if self.class.before_deliver_callbacks.any? { |callback| callback.call(self) == false }
|
|
109
|
+
|
|
98
110
|
Provider.new(
|
|
99
111
|
provider: @provider,
|
|
100
112
|
api_key: @api_key,
|
|
101
113
|
options: @options,
|
|
102
114
|
provider_options: Courrier.configuration&.providers&.[](@provider.to_s.downcase.to_sym),
|
|
103
|
-
context_options: @context_options
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
context_options: @context_options,
|
|
116
|
+
custom_headers: self.class.headers
|
|
117
|
+
).deliver.tap do |result|
|
|
118
|
+
Test.record(self, result)
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
if delivery_disabled?
|
|
110
|
-
Courrier.configuration&.logger&.info "[Courrier] Email delivery skipped: delivery is disabled via environment variable"
|
|
111
|
-
|
|
112
|
-
return nil
|
|
120
|
+
self.class.after_deliver_callbacks.each { |callback| callback.call(self, result) }
|
|
113
121
|
end
|
|
114
|
-
|
|
115
|
-
data = {
|
|
116
|
-
email_class: self.class.name,
|
|
117
|
-
provider: @provider,
|
|
118
|
-
api_key: @api_key,
|
|
119
|
-
options: @options.to_h,
|
|
120
|
-
provider_options: Courrier.configuration&.providers&.[](@provider.to_s.downcase.to_sym),
|
|
121
|
-
context_options: @context_options
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
job = Courrier::Jobs::EmailDeliveryJob
|
|
125
|
-
job = job.set(**self.class.queue_options) if self.class.queue_options.any?
|
|
126
|
-
|
|
127
|
-
job.perform_later(data)
|
|
128
|
-
rescue => error
|
|
129
|
-
raise Courrier::BackgroundDeliveryError, "Failed to enqueue email: #{error.message}"
|
|
130
122
|
end
|
|
123
|
+
alias_method :deliver_now, :deliver
|
|
131
124
|
|
|
132
125
|
private
|
|
133
126
|
|
|
@@ -137,7 +130,9 @@ module Courrier
|
|
|
137
130
|
|
|
138
131
|
def method_missing(name, *)
|
|
139
132
|
if name == :text || name == :html
|
|
140
|
-
render_template(name.to_s)
|
|
133
|
+
render_template(name.to_s).tap do |result|
|
|
134
|
+
return result || markdown_rendered if name == :html
|
|
135
|
+
end
|
|
141
136
|
else
|
|
142
137
|
@context_options[name]
|
|
143
138
|
end
|
|
@@ -149,6 +144,23 @@ module Courrier
|
|
|
149
144
|
File.exist?(template_path) ? ERB.new(File.read(template_path)).result(binding) : nil
|
|
150
145
|
end
|
|
151
146
|
|
|
147
|
+
def render_markdown_template
|
|
148
|
+
%w[md markdown].each do |ext|
|
|
149
|
+
template_path = template_file_path(ext)
|
|
150
|
+
|
|
151
|
+
return ERB.new(File.read(template_path)).result(binding) if File.exist?(template_path)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def markdown_rendered
|
|
158
|
+
return unless Courrier::Markdown.available?
|
|
159
|
+
|
|
160
|
+
markdown_content = render_markdown_template || (respond_to?(:markdown, true) ? markdown : nil)
|
|
161
|
+
Courrier::Markdown.render(markdown_content) if markdown_content
|
|
162
|
+
end
|
|
163
|
+
|
|
152
164
|
def template_file_path(format)
|
|
153
165
|
class_path = self.class.name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
154
166
|
|
data/lib/courrier/errors.rb
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Markdown
|
|
5
|
+
class << self
|
|
6
|
+
def available?
|
|
7
|
+
defined?(::Redcarpet) || defined?(::Kramdown) || defined?(::Commonmarker)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def render(text)
|
|
11
|
+
return unless available?
|
|
12
|
+
|
|
13
|
+
parser.parse(text.to_s)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def parser
|
|
19
|
+
@parser ||= available_parser.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def available_parser
|
|
23
|
+
return RedcarpetParser if defined?(::Redcarpet)
|
|
24
|
+
return KramdownParser if defined?(::Kramdown)
|
|
25
|
+
return CommonmarkerParser if defined?(::Commonmarker)
|
|
26
|
+
|
|
27
|
+
Parser
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Parser
|
|
32
|
+
def parse(text) = text.to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class RedcarpetParser < Parser
|
|
36
|
+
def parse(text)
|
|
37
|
+
renderer = Redcarpet::Render::HTML.new
|
|
38
|
+
markdown = Redcarpet::Markdown.new(renderer)
|
|
39
|
+
|
|
40
|
+
markdown.render(text)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class KramdownParser < Parser
|
|
45
|
+
def parse(text) = Kramdown::Document.new(text).to_html
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class CommonmarkerParser < Parser
|
|
49
|
+
def parse(text) = Commonmarker.to_html(text)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
module Test
|
|
5
|
+
Delivery = Data.define(:email_class, :to, :from, :reply_to, :cc, :bcc, :subject, :body, :headers, :provider, :result, :timestamp) do
|
|
6
|
+
def success?
|
|
7
|
+
result.success?
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def deliveries
|
|
13
|
+
@deliveries ||= []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clear!
|
|
17
|
+
@deliveries = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def record(email, result)
|
|
21
|
+
deliveries << Delivery.new(
|
|
22
|
+
email_class: email.class.name,
|
|
23
|
+
to: email.options.to,
|
|
24
|
+
from: email.options.from,
|
|
25
|
+
reply_to: email.options.reply_to,
|
|
26
|
+
cc: email.options.cc,
|
|
27
|
+
bcc: email.options.bcc,
|
|
28
|
+
subject: email.options.subject,
|
|
29
|
+
body: {text: email.options.text, html: email.options.html},
|
|
30
|
+
headers: email.class.headers,
|
|
31
|
+
provider: email.provider,
|
|
32
|
+
result: result,
|
|
33
|
+
timestamp: Time.now
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
module TestHelper
|
|
5
|
+
def assert_emails_delivered(count)
|
|
6
|
+
actual = Test.deliveries.size
|
|
7
|
+
|
|
8
|
+
assert_equal count, actual, "Expected #{count} email(s) to be delivered, but #{actual} were delivered"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def assert_no_emails_delivered
|
|
12
|
+
assert_emails_delivered(0)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def assert_email_delivered(email_class = nil, to: nil, from: nil, subject: nil, provider: nil)
|
|
16
|
+
deliveries = Test.deliveries
|
|
17
|
+
|
|
18
|
+
matching = deliveries.find do |delivery|
|
|
19
|
+
match_email_class(email_class, delivery.email_class) &&
|
|
20
|
+
match_recipient(to, delivery.to) &&
|
|
21
|
+
match_recipient(from, delivery.from) &&
|
|
22
|
+
match_subject(subject, delivery.subject) &&
|
|
23
|
+
match_provider(provider, delivery.provider)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
assert matching, assertion_message(email_class, to: to, from: from, subject: subject, provider: provider, deliveries: deliveries)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def match_email_class(expected, actual)
|
|
32
|
+
return true if expected.nil?
|
|
33
|
+
|
|
34
|
+
expected == actual || (expected.is_a?(Class) && actual == expected.name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def match_recipient(expected, actual)
|
|
38
|
+
return true if expected.nil?
|
|
39
|
+
|
|
40
|
+
actual.to_s.include?(expected.to_s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def match_subject(expected, actual)
|
|
44
|
+
return true if expected.nil?
|
|
45
|
+
|
|
46
|
+
actual.to_s.include?(expected.to_s)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def match_provider(expected, actual)
|
|
50
|
+
return true if expected.nil?
|
|
51
|
+
|
|
52
|
+
actual.to_s == expected.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def assertion_message(email_class, to:, from:, subject:, provider:, deliveries:)
|
|
56
|
+
"Expected email matching #{[].tap do |criteria|
|
|
57
|
+
criteria << "email_class=#{email_class}" if email_class
|
|
58
|
+
criteria << "to=#{to}" if to
|
|
59
|
+
criteria << "from=#{from}" if from
|
|
60
|
+
criteria << "subject=#{subject}" if subject
|
|
61
|
+
criteria << "provider=#{provider}" if provider
|
|
62
|
+
end.join(", ")} but none found. #{deliveries.size} email(s) delivered."
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/courrier/version.rb
CHANGED
data/lib/courrier.rb
CHANGED
|
@@ -5,8 +5,8 @@ require "courrier/errors"
|
|
|
5
5
|
require "courrier/configuration"
|
|
6
6
|
require "courrier/email"
|
|
7
7
|
require "courrier/subscriber"
|
|
8
|
-
require "courrier/
|
|
9
|
-
require "courrier/
|
|
8
|
+
require "courrier/test"
|
|
9
|
+
require "courrier/test_helper"
|
|
10
10
|
|
|
11
11
|
module Courrier
|
|
12
12
|
end
|