gardelea-email_spec 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
+