gardelea-email_spec 1.3.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.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ begin
6
+ require 'cucumber/rake/task'
7
+ Cucumber::Rake::Task.new(:features)
8
+ rescue LoadError
9
+ task :features do
10
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
11
+ end
12
+ end
13
+
14
+ require 'rspec/core/rake_task'
15
+ RSpec::Core::RakeTask.new
16
+
17
+ task :default => [:features, :spec]
data/lib/email-spec.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'email_spec'))
data/lib/email_spec.rb ADDED
@@ -0,0 +1,18 @@
1
+ unless defined?(Pony) or defined?(ActionMailer)
2
+ Kernel.warn("Neither Pony nor ActionMailer appear to be loaded so email-spec is requiring ActionMailer.")
3
+ require 'action_mailer'
4
+ end
5
+
6
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
7
+
8
+ require 'rspec'
9
+ require 'mail'
10
+ require 'email_spec/background_processes'
11
+ require 'email_spec/deliveries'
12
+ require 'email_spec/address_converter'
13
+ require 'email_spec/email_viewer'
14
+ require 'email_spec/helpers'
15
+ require 'email_spec/matchers'
16
+ require 'email_spec/mail_ext'
17
+ require 'email_spec/test_observer'
18
+ require 'email_spec/errors'
@@ -0,0 +1,29 @@
1
+ require 'singleton'
2
+
3
+ module EmailSpec
4
+ class AddressConverter
5
+ include Singleton
6
+
7
+ attr_accessor :converter
8
+
9
+ # The block provided to conversion should convert to an email
10
+ # address string or return the input untouched. For example:
11
+ #
12
+ # EmailSpec::AddressConverter.instance.conversion do |input|
13
+ # if input.is_a?(User)
14
+ # input.email
15
+ # else
16
+ # input
17
+ # end
18
+ # end
19
+ #
20
+ def conversion(&block)
21
+ self.converter = block
22
+ end
23
+
24
+ def convert(input)
25
+ return input unless converter
26
+ converter.call(input)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module EmailSpec
2
+ module BackgroundProcesses
3
+ module DelayedJob
4
+ def all_emails
5
+ work_off_queue
6
+ super
7
+ end
8
+
9
+ def last_email_sent
10
+ work_off_queue
11
+ super
12
+ end
13
+
14
+ def reset_mailer
15
+ work_off_queue
16
+ super
17
+ end
18
+
19
+ def mailbox_for(address)
20
+ work_off_queue
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ # Later versions of DelayedJob switch from using Delayed::Job to Delayed::Worker
27
+ # Support both versions for those who haven't upgraded yet
28
+ def work_off_queue
29
+ if Delayed::Worker.instance_methods.detect{|iv| iv.to_s == "work_off" }
30
+ Delayed::Worker.send :public, :work_off
31
+ worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
32
+ worker.work_off
33
+ else
34
+ Delayed::Job.work_off
35
+ end
36
+ end
37
+ end
38
+
39
+ module Compatibility
40
+ if defined?(Delayed) && (defined?(Delayed::Job) || defined?(Delayed::Worker))
41
+ include EmailSpec::BackgroundProcesses::DelayedJob
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # require this in your env.rb file after you require cucumber/rails/world
2
+
3
+ # Global Setup
4
+ if defined?(ActionMailer)
5
+ unless [:test, :activerecord, :cache, :file].include?(ActionMailer::Base.delivery_method)
6
+ ActionMailer::Base.register_observer(EmailSpec::TestObserver)
7
+ end
8
+ ActionMailer::Base.perform_deliveries = true
9
+
10
+ Before do
11
+ # Scenario setup
12
+ case ActionMailer::Base.delivery_method
13
+ when :test then ActionMailer::Base.deliveries.clear
14
+ when :cache then ActionMailer::Base.clear_cache
15
+ end
16
+ end
17
+ end
18
+
19
+ After do
20
+ EmailSpec::EmailViewer.save_and_open_all_raw_emails if ENV['SHOW_EMAILS']
21
+ EmailSpec::EmailViewer.save_and_open_all_html_emails if ENV['SHOW_HTML_EMAILS']
22
+ EmailSpec::EmailViewer.save_and_open_all_text_emails if ENV['SHOW_TEXT_EMAILS']
23
+ end
24
+
25
+ World(EmailSpec::Helpers)
26
+ World(EmailSpec::Matchers)
@@ -0,0 +1,91 @@
1
+ module EmailSpec
2
+ module MailerDeliveries
3
+ def all_emails
4
+ deliveries
5
+ end
6
+
7
+ def last_email_sent
8
+ deliveries.last || raise("No email has been sent!")
9
+ end
10
+
11
+ def reset_mailer
12
+ if defined?(ActionMailer) && ActionMailer::Base.delivery_method == :cache
13
+ mailer.clear_cache
14
+ else
15
+ deliveries.clear
16
+ end
17
+ end
18
+
19
+ def mailbox_for(address)
20
+ deliveries.select { |email|
21
+ (email.to && email.to.include?(address)) ||
22
+ (email.bcc && email.bcc.include?(address)) ||
23
+ (email.cc && email.cc.include?(address)) }
24
+ end
25
+
26
+ protected
27
+
28
+ def deliveries
29
+ if ActionMailer::Base.delivery_method == :cache
30
+ mailer.cached_deliveries
31
+ else
32
+ mailer.deliveries
33
+ end
34
+ end
35
+ end
36
+
37
+ module ARMailerDeliveries
38
+ def all_emails
39
+ Email.all.map{ |email| parse_to_mail(email) }
40
+ end
41
+
42
+ def last_email_sent
43
+ if email = Email.last
44
+ Mail.read(email.mail)
45
+ else
46
+ raise("No email has been sent!")
47
+ end
48
+ end
49
+
50
+ def reset_mailer
51
+ Email.delete_all
52
+ end
53
+
54
+ def mailbox_for(address)
55
+ Email.all.select { |email|
56
+ (email.to && email.to.include?(address)) ||
57
+ (email.bcc && email.bcc.include?(address)) ||
58
+ (email.cc && email.cc.include?(address)) }.map{ |email| parse_to_mail(email) }
59
+ end
60
+
61
+ def parse_to_mail(email)
62
+ Mail.read(email.mail)
63
+ end
64
+ end
65
+
66
+ if defined?(Pony)
67
+ module ::Pony
68
+ def self.deliveries
69
+ @deliveries ||= []
70
+ end
71
+
72
+ def self.mail(options)
73
+ deliveries << build_mail(options)
74
+ end
75
+ end
76
+ end
77
+
78
+ module Deliveries
79
+ if defined?(Pony)
80
+ def deliveries; Pony::deliveries ; end
81
+ include EmailSpec::MailerDeliveries
82
+ elsif ActionMailer::Base.delivery_method == :activerecord
83
+ include EmailSpec::ARMailerDeliveries
84
+ else
85
+ def mailer; ActionMailer::Base; end
86
+ include EmailSpec::MailerDeliveries
87
+ end
88
+ include EmailSpec::BackgroundProcesses::Compatibility
89
+ end
90
+ end
91
+
@@ -0,0 +1,91 @@
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.include?('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.include?('text/plain') }
36
+ if m.respond_to?(:ordered_each) # Rails 2 / TMail
37
+ m.ordered_each{|k,v| f.write "#{k}: #{v}\n" }
38
+ else # Rails 3 / Mail
39
+ f.write(text_part.header.to_s + "\n")
40
+ end
41
+
42
+ f.write text_part.body
43
+ else
44
+ f.write m.to_s
45
+ end
46
+ f.write "\n" + '='*80 + "\n"
47
+ end
48
+ end
49
+
50
+ open_in_text_editor(filename)
51
+ end
52
+
53
+ def self.save_and_open_email(mail)
54
+ filename = tmp_email_filename
55
+
56
+ File.open(filename, "w") do |f|
57
+ f.write mail.to_s
58
+ end
59
+
60
+ open_in_text_editor(filename)
61
+ end
62
+
63
+ def self.save_and_open_email_attachments_list(mail)
64
+ filename = tmp_email_filename
65
+
66
+ File.open(filename, "w") do |f|
67
+ mail.attachments.each_with_index do |attachment, index|
68
+ info = "#{index + 1}:"
69
+ info += "\n\tfilename: #{attachment.original_filename}"
70
+ info += "\n\tcontent type: #{attachment.content_type}"
71
+ info += "\n\tsize: #{attachment.size}"
72
+ f.write info + "\n"
73
+ end
74
+ end
75
+
76
+ open_in_text_editor(filename)
77
+ end
78
+
79
+ def self.open_in_text_editor(filename)
80
+ Launchy.open(URI.parse("file://#{File.expand_path(filename)}"), :application => :editor)
81
+ end
82
+
83
+ def self.open_in_browser(filename)
84
+ Launchy.open(URI.parse("file://#{File.expand_path(filename)}"))
85
+ end
86
+
87
+ def self.tmp_email_filename(extension = '.txt')
88
+ "#{Rails.root}/tmp/email-#{Time.now.to_i}#{extension}"
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ module EmailSpec
2
+ class CouldNotFindEmailError < StandardError
3
+ end
4
+
5
+ class NoEmailAddressProvided < StandardError
6
+ end
7
+ end
@@ -0,0 +1,175 @@
1
+ require 'uri'
2
+ require 'email_spec/deliveries'
3
+
4
+ module EmailSpec
5
+
6
+ module Helpers
7
+ include Deliveries
8
+
9
+ def visit_in_email(link_text)
10
+ visit(parse_email_for_link(current_email, link_text))
11
+ end
12
+
13
+ def click_email_link_matching(regex, email = current_email)
14
+ url = links_in_email(email).detect { |link| link =~ regex }
15
+ raise "No link found matching #{regex.inspect} in #{email.default_part_body}" unless url
16
+ visit request_uri(url)
17
+ end
18
+
19
+ def click_first_link_in_email(email = current_email)
20
+ link = links_in_email(email).first
21
+ visit request_uri(link)
22
+ end
23
+
24
+ def open_email(address, opts={})
25
+ set_current_email(find_email!(address, opts))
26
+ end
27
+
28
+ alias_method :open_email_for, :open_email
29
+
30
+ def open_last_email
31
+ set_current_email(last_email_sent)
32
+ end
33
+
34
+ def open_last_email_for(address)
35
+ set_current_email(mailbox_for(address).last)
36
+ end
37
+
38
+ def current_email(address=nil)
39
+ address = convert_address(address)
40
+ email = address ? email_spec_hash[:current_emails][address] : email_spec_hash[:current_email]
41
+ raise RSpec::Expectations::ExpectationNotMetError, "Expected an open email but none was found. Did you forget to call open_email?" unless email
42
+ email
43
+ end
44
+
45
+ def current_email_attachments(address=nil)
46
+ current_email(address).attachments || Array.new
47
+ end
48
+
49
+ def unread_emails_for(address)
50
+ mailbox_for(address) - read_emails_for(address)
51
+ end
52
+
53
+ def read_emails_for(address)
54
+ email_spec_hash[:read_emails][convert_address(address)] ||= []
55
+ end
56
+
57
+ # Should be able to accept String or Regexp options.
58
+ def find_email(address, opts={})
59
+ address = convert_address(address)
60
+ if opts[:with_subject]
61
+ expected_subject = (opts[:with_subject].is_a?(String) ? Regexp.escape(opts[:with_subject]) : opts[:with_subject])
62
+ mailbox_for(address).find { |m| m.subject =~ Regexp.new(expected_subject) }
63
+ elsif opts[:with_text]
64
+ expected_text = (opts[:with_text].is_a?(String) ? Regexp.escape(opts[:with_text]) : opts[:with_text])
65
+ mailbox_for(address).find { |m| m.default_part_body =~ Regexp.new(expected_text) }
66
+ else
67
+ mailbox_for(address).first
68
+ end
69
+ end
70
+
71
+ def links_in_email(email)
72
+ URI.extract(email.default_part_body.to_s, ['http', 'https'])
73
+ end
74
+
75
+ private
76
+
77
+ def email_spec_hash
78
+ @email_spec_hash ||= {:read_emails => {}, :unread_emails => {}, :current_emails => {}, :current_email => nil}
79
+ end
80
+
81
+ def find_email!(address, opts={})
82
+ email = find_email(address, opts)
83
+ if current_email_address.nil?
84
+ raise EmailSpec::NoEmailAddressProvided, "No email address has been provided. Make sure current_email_address is returning something."
85
+ elsif email.nil?
86
+ error = "#{opts.keys.first.to_s.humanize.downcase unless opts.empty?} #{('"' + opts.values.first.to_s + '"') unless opts.empty?}"
87
+ raise EmailSpec::CouldNotFindEmailError, "Could not find email #{error} in the mailbox for #{current_email_address}. \n Found the following emails:\n\n #{all_emails.to_s}"
88
+ end
89
+ email
90
+ end
91
+
92
+ def set_current_email(email)
93
+ return unless email
94
+ [email.to, email.cc, email.bcc].compact.flatten.each do |to|
95
+ read_emails_for(to) << email
96
+ email_spec_hash[:current_emails][to] = email
97
+ end
98
+ email_spec_hash[:current_email] = email
99
+ end
100
+
101
+ def parse_email_for_link(email, text_or_regex)
102
+ email.should have_body_text(text_or_regex)
103
+
104
+ url = parse_email_for_explicit_link(email, text_or_regex)
105
+ url ||= parse_email_for_anchor_text_link(email, text_or_regex)
106
+
107
+ raise "No link found matching #{text_or_regex.inspect} in #{email}" unless url
108
+ url
109
+ end
110
+
111
+ def request_uri(link)
112
+ return unless link
113
+ url = URI::parse(link)
114
+ url.fragment ? (url.request_uri + "#" + url.fragment) : url.request_uri
115
+ end
116
+
117
+ # e.g. confirm in http://confirm
118
+ def parse_email_for_explicit_link(email, regex)
119
+ regex = /#{Regexp.escape(regex)}/ unless regex.is_a?(Regexp)
120
+ url = links_in_email(email).detect { |link| link =~ regex }
121
+ request_uri(url)
122
+ end
123
+
124
+ # e.g. Click here in <a href="http://confirm">Click here</a>
125
+ def parse_email_for_anchor_text_link(email, link_text)
126
+ if textify_images(email.default_part_body) =~ %r{<a[^>]*href=['"]?([^'"]*)['"]?[^>]*?>[^<]*?#{link_text}[^<]*?</a>}
127
+ URI.split($1)[5..-1].compact!.join("?").gsub("&amp;", "&")
128
+ # sub correct ampersand after rails switches it (http://dev.rubyonrails.org/ticket/4002)
129
+ else
130
+ return nil
131
+ end
132
+ end
133
+
134
+ def textify_images(email_body)
135
+ email_body.to_s.gsub(%r{<img[^>]*alt=['"]?([^'"]*)['"]?[^>]*?/>}) { $1 }
136
+ end
137
+
138
+ def parse_email_count(amount)
139
+ case amount
140
+ when "no"
141
+ 0
142
+ when "an"
143
+ 1
144
+ else
145
+ amount.to_i
146
+ end
147
+ end
148
+
149
+ attr_reader :last_email_address
150
+
151
+ def convert_address(address)
152
+ @last_email_address = (address || current_email_address)
153
+ AddressConverter.instance.convert(@last_email_address)
154
+ end
155
+
156
+ # Overwrite this method to set default email address, for example:
157
+ # last_email_address || @current_user.email
158
+ def current_email_address
159
+ last_email_address
160
+ end
161
+
162
+
163
+ def mailbox_for(address)
164
+ super(convert_address(address)) # super resides in Deliveries
165
+ end
166
+
167
+ def email_spec_deprecate(text)
168
+ puts ""
169
+ puts "DEPRECATION: #{text.split.join(' ')}"
170
+ puts ""
171
+ end
172
+
173
+ end
174
+ end
175
+