tape 0.0.1

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.
@@ -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
+