tape 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008-2009 Ben Mabey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ Tape
2
+ ====
3
+
4
+ A rewrite of email\_spec that borrows a lot but gets rid of bloat. Currently ships with the following adapters:
5
+
6
+ * ActionMailer (:test, :active\_record, :maildir)
7
+ * Pony
8
+
9
+ Implementing your own adapter is easy. See below.
10
+
11
+ It does *NOT* handle Delayed::Job implicitly. Trigger your workers before checking for mail.
12
+
13
+ Compatibility
14
+ -------------
15
+
16
+ For convenience, `email_spec` helpers and matchers are available in the `Tape::EmailSpec` module. Use them if you want to migrate an existing project from `email_spec` to `tape`.
17
+
18
+ Example
19
+ -------
20
+
21
+ require 'tape'
22
+
23
+ # This will set up your adapter
24
+ Tape.configure 'action_mailer/test'
25
+
26
+ # Get all mails
27
+ Tape.adapter.all
28
+
29
+ # Get last mail
30
+ Tape.adapter.last
31
+
32
+ # Clear mails
33
+ Tape.adapter.reset
34
+
35
+
36
+ Implementing an adapter
37
+ -----------------------
38
+
39
+ Adapters inherit from Tape::Adapters::Base and implement only three self-explanatory methods:
40
+
41
+ * all
42
+ * last
43
+ * reset
@@ -0,0 +1,33 @@
1
+ module Tape
2
+
3
+ module EmailSpec
4
+ autoload :Helpers, 'tape/email_spec/helpers'
5
+ autoload :Matchers, 'tape/email_spec/matchers'
6
+ autoload :AddressConverter, 'tape/email_spec/address_converter'
7
+ end
8
+
9
+ module Adapters
10
+ autoload :Base, 'tape/adapters/base'
11
+ autoload :Pony, 'tape/adapters/pony'
12
+
13
+ module ActionMailer
14
+ autoload :Cache, 'tape/adapters/action_mailer/cache'
15
+ autoload :Maildir, 'tape/adapters/action_mailer/maildir'
16
+ autoload :Test, 'tape/adapters/action_mailer/test'
17
+ end
18
+ end
19
+
20
+ def self.configure(adapter, options = {})
21
+ klass = "Tape::Adapters::#{adapter.to_s.camelize}".constantize
22
+ @adapter = klass.new(options)
23
+ end
24
+
25
+ def self.adapter
26
+ @adapter || begin
27
+ raise "You need to call Tape.configure to set up your adapter"
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ require 'tape/mail_ext'
@@ -0,0 +1,19 @@
1
+ module Tape
2
+
3
+ module Adapters
4
+
5
+ def all_emails
6
+ Tape.adapter.all
7
+ end
8
+
9
+ def reset_mailer
10
+ Tape.adapter.reset
11
+ end
12
+
13
+ def mailbox_for(address)
14
+ Tape.adapter.by_recipient address
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,45 @@
1
+ module Tape
2
+
3
+ module Adapters
4
+
5
+ module ActionMailer
6
+
7
+ # For ActionMailer with delivery_method = :activerecord
8
+ class ActiveRecord
9
+
10
+ def initialize(options)
11
+ super options
12
+
13
+ # Default to Email if no model is configured.
14
+ unless self.options[:model]
15
+ self.options[:model] if const_defined?(Email)
16
+ end
17
+ end
18
+
19
+ def all
20
+ model.all.map { |email| parse email.mail }
21
+ end
22
+
23
+ def last
24
+ if email = model.last
25
+ parse mail
26
+ end
27
+ end
28
+
29
+ def reset
30
+ model.delete_all
31
+ end
32
+
33
+ protected
34
+
35
+ def model
36
+ options[:model]
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,28 @@
1
+ module Tape
2
+
3
+ module Adapteres
4
+
5
+ module ActionMailer
6
+
7
+ # For ActionMailer with delivery_method = :cache
8
+ class Cache < Base
9
+
10
+ def all
11
+ ActionMailer::Base.cached_deliveries
12
+ end
13
+
14
+ def last
15
+ ActionMailer::Base.cached_deliveries.last
16
+ end
17
+
18
+ def reset
19
+ ActionMailer::Base.clear_cache
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'maildir'
2
+
3
+ module Tape
4
+
5
+ module Adapters
6
+
7
+ module ActionMailer
8
+
9
+ # For ActionMailer with delivery_method = :maildir.
10
+ class Maildir < Base
11
+
12
+ def initialize(options = {})
13
+ super options
14
+ @maildir = ::Maildir.new(options[:path])
15
+ @maildir.serializer = ::Maildir::Serializer::Mail.new
16
+ end
17
+
18
+ def all
19
+ @maildir.list(:new).collect(&:data)
20
+ end
21
+
22
+ def last
23
+ # FIXME: Sort by date
24
+ all.last
25
+ end
26
+
27
+ def reset
28
+ @maildir.list(:new).each do |message|
29
+ message.destroy
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,28 @@
1
+ module Tape
2
+
3
+ module Adapters
4
+
5
+ module ActionMailer
6
+
7
+ # For ActionMailer with delivery_method = :test
8
+ class Test < Base
9
+
10
+ def all
11
+ ActionMailer::Base.deliveries
12
+ end
13
+
14
+ def last
15
+ ActionMailer::Base.deliveries.last
16
+ end
17
+
18
+ def reset
19
+ ActionMailer::Base.deliveries.clear
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,39 @@
1
+ module Tape
2
+
3
+ module Adapters
4
+
5
+ class Base
6
+
7
+ attr_accessor :options
8
+
9
+ def initialize(options)
10
+ self.options = options
11
+ end
12
+
13
+ def all
14
+ raise "Not implemented"
15
+ end
16
+
17
+ # Returns the last sent mail or nil if there is none.
18
+ def last
19
+ raise "Not implemented"
20
+ end
21
+
22
+ def reset
23
+ raise "Not implemented"
24
+ end
25
+
26
+ def by_recipient(address)
27
+ all.select do |mail|
28
+ mail.destinations.include?(address)
29
+ end
30
+ end
31
+
32
+ def parse(mail)
33
+ Mail.read mail
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,36 @@
1
+ module Tape
2
+
3
+ module Adapters
4
+ # Using Pony
5
+ class Pony < Base
6
+
7
+ def all
8
+ ::Pony.deliveries
9
+ end
10
+
11
+ def last
12
+ ::Pony.deliveries.last
13
+ end
14
+
15
+ def reset
16
+ ::Pony.deliveries.clear
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ # Monkey-patch pony to not really send mails but store them.
26
+ if defined?(Pony)
27
+ module ::Pony
28
+ def self.deliveries
29
+ @deliveries ||= []
30
+ end
31
+
32
+ def self.mail(options)
33
+ deliveries << build_mail(options)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ After do
2
+ Tape.adapter.reset
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'singleton'
2
+
3
+ module Tape
4
+ module EmailSpec
5
+ class AddressConverter
6
+ include Singleton
7
+
8
+ attr_accessor :converter
9
+
10
+ # The block provided to conversion should convert to an email
11
+ # address string or return the input untouched. For example:
12
+ #
13
+ # EmailSpec::AddressConverter.instance.conversion do |input|
14
+ # if input.is_a?(User)
15
+ # input.email
16
+ # else
17
+ # input
18
+ # end
19
+ # end
20
+ #
21
+ def conversion(&block)
22
+ self.converter = block
23
+ end
24
+
25
+ def convert(input)
26
+ return input unless converter
27
+ converter.call(input)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,175 @@
1
+ require 'uri'
2
+
3
+ module Tape
4
+
5
+ module EmailSpec
6
+
7
+ module Helpers
8
+
9
+ def all_emails
10
+ Tape.adapter.all
11
+ end
12
+
13
+ def last_email_sent
14
+ Tape.adapter.last
15
+ end
16
+
17
+ def visit_in_email(link_text)
18
+ visit(parse_email_for_link(current_email, link_text))
19
+ end
20
+
21
+ def click_email_link_matching(regex, email = current_email)
22
+ url = links_in_email(email).detect { |link| link =~ regex }
23
+ raise "No link found matching #{regex.inspect} in #{email.default_part_body}" unless url
24
+ visit request_uri(url)
25
+ end
26
+
27
+ def click_first_link_in_email(email = current_email)
28
+ link = links_in_email(email).first
29
+ visit request_uri(link)
30
+ end
31
+
32
+ def open_email(address, opts={})
33
+ set_current_email(find_email!(address, opts))
34
+ end
35
+
36
+ alias_method :open_email_for, :open_email
37
+
38
+ def open_last_email
39
+ set_current_email(last_email_sent)
40
+ end
41
+
42
+ def open_last_email_for(address)
43
+ set_current_email(mailbox_for(address).last)
44
+ end
45
+
46
+ def current_email(address=nil)
47
+ address = convert_address(address)
48
+ email = address ? email_spec_hash[:current_emails][address] : email_spec_hash[:current_email]
49
+ raise RSpec::Expectations::ExpectationNotMetError, "Expected an open email but none was found. Did you forget to call open_email?" unless email
50
+ email
51
+ end
52
+
53
+ def current_email_attachments(address=nil)
54
+ current_email(address).attachments || Array.new
55
+ end
56
+
57
+ def unread_emails_for(address)
58
+ mailbox_for(address) - read_emails_for(address)
59
+ end
60
+
61
+ def read_emails_for(address)
62
+ email_spec_hash[:read_emails][convert_address(address)] ||= []
63
+ end
64
+
65
+ def find_email(address, opts={})
66
+ address = convert_address(address)
67
+ if opts[:with_subject]
68
+ mailbox_for(address).find { |m| m.subject =~ Regexp.new(opts[:with_subject]) }
69
+ elsif opts[:with_text]
70
+ mailbox_for(address).find { |m| m.default_part_body =~ Regexp.new(opts[:with_text]) }
71
+ else
72
+ mailbox_for(address).first
73
+ end
74
+ end
75
+
76
+ def links_in_email(email)
77
+ URI.extract(email.default_part_body.to_s, ['http', 'https'])
78
+ end
79
+
80
+ private
81
+
82
+ def email_spec_hash
83
+ @email_spec_hash ||= {:read_emails => {}, :unread_emails => {}, :current_emails => {}, :current_email => nil}
84
+ end
85
+
86
+ def find_email!(address, opts={})
87
+ email = find_email(address, opts)
88
+ if email.nil?
89
+ error = "#{opts.keys.first.to_s.humanize unless opts.empty?} #{('"' + opts.values.first.to_s.humanize + '"') unless opts.empty?}"
90
+ raise RSpec::Expectations::ExpectationNotMetError, "Could not find email #{error}. \n Found the following emails:\n\n #{all_emails.to_s}"
91
+ end
92
+ email
93
+ end
94
+
95
+ def set_current_email(email)
96
+ return unless email
97
+ [email.to, email.cc, email.bcc].compact.flatten.each do |to|
98
+ read_emails_for(to) << email
99
+ email_spec_hash[:current_emails][to] = email
100
+ end
101
+ email_spec_hash[:current_email] = email
102
+ end
103
+
104
+ def parse_email_for_link(email, text_or_regex)
105
+ email.should have_body_text(text_or_regex)
106
+
107
+ url = parse_email_for_explicit_link(email, text_or_regex)
108
+ url ||= parse_email_for_anchor_text_link(email, text_or_regex)
109
+
110
+ raise "No link found matching #{text_or_regex.inspect} in #{email}" unless url
111
+ url
112
+ end
113
+
114
+ def request_uri(link)
115
+ return unless link
116
+ url = URI::parse(link)
117
+ url.fragment ? (url.request_uri + "#" + url.fragment) : url.request_uri
118
+ end
119
+
120
+ # e.g. confirm in http://confirm
121
+ def parse_email_for_explicit_link(email, regex)
122
+ regex = /#{Regexp.escape(regex)}/ unless regex.is_a?(Regexp)
123
+ url = links_in_email(email).detect { |link| link =~ regex }
124
+ request_uri(url)
125
+ end
126
+
127
+ # e.g. Click here in <a href="http://confirm">Click here</a>
128
+ def parse_email_for_anchor_text_link(email, link_text)
129
+ if textify_images(email.default_part_body) =~ %r{<a[^>]*href=['"]?([^'"]*)['"]?[^>]*?>[^<]*?#{link_text}[^<]*?</a>}
130
+ URI.split($1)[5..-1].compact!.join("?").gsub("&amp;", "&")
131
+ # sub correct ampersand after rails switches it (http://dev.rubyonrails.org/ticket/4002)
132
+ else
133
+ return nil
134
+ end
135
+ end
136
+
137
+ def textify_images(email_body)
138
+ email_body.to_s.gsub(%r{<img[^>]*alt=['"]?([^'"]*)['"]?[^>]*?/>}) { $1 }
139
+ end
140
+
141
+ def parse_email_count(amount)
142
+ case amount
143
+ when "no"
144
+ 0
145
+ when "an"
146
+ 1
147
+ else
148
+ amount.to_i
149
+ end
150
+ end
151
+
152
+ attr_reader :last_email_address
153
+
154
+ def convert_address(address)
155
+ @last_email_address = (address || current_email_address)
156
+ AddressConverter.instance.convert(@last_email_address)
157
+ end
158
+
159
+
160
+ def mailbox_for(address)
161
+ super(convert_address(address)) # super resides in Deliveries
162
+ end
163
+
164
+ def email_spec_deprecate(text)
165
+ puts ""
166
+ puts "DEPRECATION: #{text.split.join(' ')}"
167
+ puts ""
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+
@@ -0,0 +1,259 @@
1
+ module Tape
2
+ module EmailSpec
3
+ module Matchers
4
+ class ReplyTo
5
+ def initialize(email)
6
+ @expected_reply_to = Mail::ReplyToField.new(email).addrs.first
7
+ end
8
+
9
+ def description
10
+ "have reply to as #{@expected_reply_to.address}"
11
+ end
12
+
13
+ def matches?(email)
14
+ @email = email
15
+ @actual_reply_to = (email.reply_to || []).first
16
+ !@actual_reply_to.nil? &&
17
+ @actual_reply_to == @expected_reply_to.address
18
+ end
19
+
20
+ def failure_message
21
+ "expected #{@email.inspect} to reply to #{@expected_reply_to.address.inspect}, but it replied to #{@actual_reply_to.inspect}"
22
+ end
23
+
24
+ def negative_failure_message
25
+ "expected #{@email.inspect} not to deliver to #{@expected_reply_to.address.inspect}, but it did"
26
+ end
27
+ end
28
+
29
+ def reply_to(email)
30
+ ReplyTo.new(email)
31
+ end
32
+
33
+ alias :have_reply_to :reply_to
34
+
35
+ class DeliverTo
36
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
37
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
38
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
39
+ end
40
+
41
+ @expected_recipients = Mail::ToField.new(emails).addrs.map(&:to_s).sort
42
+ end
43
+
44
+ def description
45
+ "be delivered to #{@expected_recipients.inspect}"
46
+ end
47
+
48
+ def matches?(email)
49
+ @email = email
50
+ @actual_recipients = (email.header[:to].addrs || []).map(&:to_s).sort
51
+ @actual_recipients == @expected_recipients
52
+ end
53
+
54
+ def failure_message
55
+ "expected #{@email.inspect} to deliver to #{@expected_recipients.inspect}, but it delivered to #{@actual_recipients.inspect}"
56
+ end
57
+
58
+ def negative_failure_message
59
+ "expected #{@email.inspect} not to deliver to #{@expected_recipients.inspect}, but it did"
60
+ end
61
+ end
62
+
63
+ def deliver_to(*expected_email_addresses_or_objects_that_respond_to_email)
64
+ DeliverTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
65
+ end
66
+
67
+ alias :be_delivered_to :deliver_to
68
+
69
+ class DeliverFrom
70
+
71
+ def initialize(email)
72
+ @expected_sender = Mail::FromField.new(email).addrs.first
73
+ end
74
+
75
+ def description
76
+ "be delivered from #{@expected_sender}"
77
+ end
78
+
79
+ def matches?(email)
80
+ @email = email
81
+ @actual_sender = (email.header[:from].addrs || []).first
82
+
83
+ !@actual_sender.nil? &&
84
+ @actual_sender.to_s == @expected_sender.to_s
85
+ end
86
+
87
+ def failure_message
88
+ %(expected #{@email.inspect} to deliver from "#{@expected_sender.to_s}", but it delivered from "#{@actual_sender.to_s}")
89
+ end
90
+
91
+ def negative_failure_message
92
+ %(expected #{@email.inspect} not to deliver from "#{@expected_sender.to_s}", but it did)
93
+ end
94
+ end
95
+
96
+ def deliver_from(email)
97
+ DeliverFrom.new(email)
98
+ end
99
+
100
+ alias :be_delivered_from :deliver_from
101
+
102
+ class BccTo
103
+
104
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
105
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
106
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
107
+ end
108
+
109
+ @expected_email_addresses = emails.sort
110
+ end
111
+
112
+ def description
113
+ "be bcc'd to #{@expected_email_addresses.inspect}"
114
+ end
115
+
116
+ def matches?(email)
117
+ @email = email
118
+ @actual_recipients = (Array(email.bcc) || []).sort
119
+ @actual_recipients == @expected_email_addresses
120
+ end
121
+
122
+ def failure_message
123
+ "expected #{@email.inspect} to bcc to #{@expected_email_addresses.inspect}, but it was bcc'd to #{@actual_recipients.inspect}"
124
+ end
125
+
126
+ def negative_failure_message
127
+ "expected #{@email.inspect} not to bcc to #{@expected_email_addresses.inspect}, but it did"
128
+ end
129
+ end
130
+
131
+ def bcc_to(*expected_email_addresses_or_objects_that_respond_to_email)
132
+ BccTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
133
+ end
134
+
135
+ class CcTo
136
+
137
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
138
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
139
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
140
+ end
141
+
142
+ @expected_email_addresses = emails.sort
143
+ end
144
+
145
+ def description
146
+ "be cc'd to #{@expected_email_addresses.inspect}"
147
+ end
148
+
149
+ def matches?(email)
150
+ @email = email
151
+ @actual_recipients = (Array(email.cc) || []).sort
152
+ @actual_recipients == @expected_email_addresses
153
+ end
154
+
155
+ def failure_message
156
+ "expected #{@email.inspect} to cc to #{@expected_email_addresses.inspect}, but it was cc'd to #{@actual_recipients.inspect}"
157
+ end
158
+
159
+ def negative_failure_message
160
+ "expected #{@email.inspect} not to cc to #{@expected_email_addresses.inspect}, but it did"
161
+ end
162
+ end
163
+
164
+ def cc_to(*expected_email_addresses_or_objects_that_respond_to_email)
165
+ CcTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
166
+ end
167
+
168
+ RSpec::Matchers.define :have_subject do
169
+ match do |given|
170
+ given_subject = given.subject
171
+ expected_subject = expected.first
172
+
173
+ if expected_subject.is_a?(String)
174
+ description { "have subject of #{expected_subject.inspect}" }
175
+ failure_message_for_should { "expected the subject to be #{expected_subject.inspect} but was #{given_subject.inspect}" }
176
+ failure_message_for_should_not { "expected the subject not to be #{expected_subject.inspect} but was" }
177
+
178
+ given_subject == expected_subject
179
+ else
180
+ description { "have subject matching #{expected_subject.inspect}" }
181
+ failure_message_for_should { "expected the subject to match #{expected_subject.inspect}, but did not. Actual subject was: #{given_subject.inspect}" }
182
+ failure_message_for_should_not { "expected the subject not to match #{expected_subject.inspect} but #{given_subject.inspect} does match it." }
183
+
184
+ !!(given_subject =~ expected_subject)
185
+ end
186
+ end
187
+ end
188
+
189
+ RSpec::Matchers.define :include_email_with_subject do
190
+ match do |given_emails|
191
+ expected_subject = expected.first
192
+
193
+ if expected_subject.is_a?(String)
194
+ description { "include email with subject of #{expected_subject.inspect}" }
195
+ failure_message_for_should { "expected at least one email to have the subject #{expected_subject.inspect} but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
196
+ failure_message_for_should_not { "expected no email with the subject #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
197
+
198
+ given_emails.map(&:subject).include?(expected_subject)
199
+ else
200
+ description { "include email with subject matching #{expected_subject.inspect}" }
201
+ failure_message_for_should { "expected at least one email to have a subject matching #{expected_subject.inspect}, but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
202
+ failure_message_for_should_not { "expected no email to have a subject matching #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
203
+
204
+ !!(given_emails.any?{ |mail| mail.subject =~ expected_subject })
205
+ end
206
+ end
207
+ end
208
+
209
+ RSpec::Matchers.define :have_body_text do
210
+ match do |given|
211
+ expected_text = expected.first
212
+
213
+ if expected_text.is_a?(String)
214
+ normalized_body = given.default_part_body.to_s.gsub(/\s+/, " ")
215
+ normalized_expected = expected_text.gsub(/\s+/, " ")
216
+ description { "have body including #{normalized_expected.inspect}" }
217
+ failure_message_for_should { "expected the body to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
218
+ failure_message_for_should_not { "expected the body not to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
219
+
220
+ normalized_body.include?(normalized_expected)
221
+ else
222
+ given_body = given.default_part_body.to_s
223
+ description { "have body matching #{expected_text.inspect}" }
224
+ failure_message_for_should { "expected the body to match #{expected_text.inspect}, but did not. Actual body was: #{given_body.inspect}" }
225
+ failure_message_for_should_not { "expected the body not to match #{expected_text.inspect} but #{given_body.inspect} does match it." }
226
+
227
+ !!(given_body =~ expected_text)
228
+ end
229
+ end
230
+ end
231
+
232
+ def mail_headers_hash(email_headers)
233
+ email_headers.fields.inject({}) { |hash, field| hash[field.field.class::FIELD_NAME] = field.to_s; hash }
234
+ end
235
+
236
+ RSpec::Matchers.define :have_header do
237
+ match do |given|
238
+ given_header = given.header
239
+ expected_name, expected_value = *expected
240
+
241
+ if expected_value.is_a?(String)
242
+ description { "have header #{expected_name}: #{expected_value}" }
243
+
244
+ failure_message_for_should { "expected the headers to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
245
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
246
+
247
+ given_header[expected_name].to_s == expected_value
248
+ else
249
+ description { "have header #{expected_name} with value matching #{expected_value.inspect}" }
250
+ failure_message_for_should { "expected the headers to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
251
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
252
+
253
+ given_header[expected_name].to_s =~ expected_value
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,87 @@
1
+ module EmailSpec
2
+ class EmailViewer
3
+ extend Deliveries
4
+
5
+ def self.save_and_open_all_raw_emails
6
+ filename = tmp_email_filename
7
+
8
+ File.open(filename, "w") do |f|
9
+ all_emails.each do |m|
10
+ f.write m.to_s
11
+ f.write "\n" + '='*80 + "\n"
12
+ end
13
+ end
14
+
15
+ open_in_text_editor(filename)
16
+ end
17
+
18
+ def self.save_and_open_all_html_emails
19
+ all_emails.each_with_index do |m, index|
20
+ if m.multipart? && html_part = m.parts.detect{ |p| p.content_type == 'text/html' }
21
+ filename = tmp_email_filename("-#{index}.html")
22
+ File.open(filename, "w") do |f|
23
+ f.write m.parts[1].body
24
+ end
25
+ open_in_browser(filename)
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.save_and_open_all_text_emails
31
+ filename = tmp_email_filename
32
+
33
+ File.open(filename, "w") do |f|
34
+ all_emails.each do |m|
35
+ if m.multipart? && text_part = m.parts.detect{ |p| p.content_type == 'text/plain' }
36
+ m.ordered_each{|k,v| f.write "#{k}: #{v}\n" }
37
+ f.write text_part.body
38
+ else
39
+ f.write m.to_s
40
+ end
41
+ f.write "\n" + '='*80 + "\n"
42
+ end
43
+ end
44
+
45
+ open_in_text_editor(filename)
46
+ end
47
+
48
+ def self.save_and_open_email(mail)
49
+ filename = tmp_email_filename
50
+
51
+ File.open(filename, "w") do |f|
52
+ f.write mail.to_s
53
+ end
54
+
55
+ open_in_text_editor(filename)
56
+ end
57
+
58
+ def self.save_and_open_email_attachments_list(mail)
59
+ filename = tmp_email_filename
60
+
61
+ File.open(filename, "w") do |f|
62
+ mail.attachments.each_with_index do |attachment, index|
63
+ info = "#{index + 1}:"
64
+ info += "\n\tfilename: #{attachment.original_filename}"
65
+ info += "\n\tcontent type: #{attachment.content_type}"
66
+ info += "\n\tsize: #{attachment.size}"
67
+ f.write info + "\n"
68
+ end
69
+ end
70
+
71
+ open_in_text_editor(filename)
72
+ end
73
+
74
+ # TODO: use the launchy gem for this stuff...
75
+ def self.open_in_text_editor(filename)
76
+ `open #{filename}`
77
+ end
78
+
79
+ def self.open_in_browser(filename)
80
+ `open #{filename}`
81
+ end
82
+
83
+ def self.tmp_email_filename(extension = '.txt')
84
+ "#{Rails.root}/tmp/email-#{Time.now.to_i}#{extension}"
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ require 'mail'
2
+
3
+ module Tape
4
+ module MailExt
5
+ def default_part
6
+ @default_part ||= html_part || text_part || self
7
+ end
8
+
9
+ def default_part_body
10
+ default_part.body
11
+ end
12
+ end
13
+ end
14
+
15
+ Mail::Message.send(:include, Tape::MailExt)
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tape
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Alexander Flatter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-10 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "3.0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: mail
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rake
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: actionmailer
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: actionmailer-maildir
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 2.0.0
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: fakefs
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id007
93
+ description: ""
94
+ email:
95
+ - aflatter@farbenmeer.net
96
+ executables: []
97
+
98
+ extensions: []
99
+
100
+ extra_rdoc_files: []
101
+
102
+ files:
103
+ - lib/tape/adapters/base.rb
104
+ - lib/tape/adapters/action_mailer/test.rb
105
+ - lib/tape/adapters/action_mailer/active_record.rb
106
+ - lib/tape/adapters/action_mailer/cache.rb
107
+ - lib/tape/adapters/action_mailer/maildir.rb
108
+ - lib/tape/adapters/pony.rb
109
+ - lib/tape/email_viewer.rb
110
+ - lib/tape/cucumber.rb
111
+ - lib/tape/adapters.asfdsf
112
+ - lib/tape/mail_ext.rb
113
+ - lib/tape/email_spec/helpers.rb
114
+ - lib/tape/email_spec/address_converter.rb
115
+ - lib/tape/email_spec/matchers.rb
116
+ - lib/tape.rb
117
+ - MIT-LICENSE
118
+ - README.md
119
+ has_rdoc: true
120
+ homepage: http://github.com/aflatter/tape
121
+ licenses: []
122
+
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: "0"
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 1.3.6
140
+ requirements: []
141
+
142
+ rubyforge_project:
143
+ rubygems_version: 1.5.0
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: ""
147
+ test_files: []
148
+