basiszwo-email_spec 0.1.3

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.
Files changed (89) hide show
  1. data/History.txt +70 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.rdoc +133 -0
  4. data/Rakefile +50 -0
  5. data/examples/rails_root/app/controllers/application.rb +15 -0
  6. data/examples/rails_root/app/controllers/welcome_controller.rb +9 -0
  7. data/examples/rails_root/app/helpers/application_helper.rb +3 -0
  8. data/examples/rails_root/app/helpers/welcome_helper.rb +2 -0
  9. data/examples/rails_root/app/models/user.rb +2 -0
  10. data/examples/rails_root/app/models/user_mailer.rb +12 -0
  11. data/examples/rails_root/app/views/user_mailer/signup.erb +3 -0
  12. data/examples/rails_root/app/views/welcome/confirm.html.erb +1 -0
  13. data/examples/rails_root/app/views/welcome/index.html.erb +8 -0
  14. data/examples/rails_root/app/views/welcome/signup.html.erb +1 -0
  15. data/examples/rails_root/config/boot.rb +109 -0
  16. data/examples/rails_root/config/database.yml +22 -0
  17. data/examples/rails_root/config/environment.rb +22 -0
  18. data/examples/rails_root/config/environments/development.rb +17 -0
  19. data/examples/rails_root/config/environments/production.rb +24 -0
  20. data/examples/rails_root/config/environments/test.rb +33 -0
  21. data/examples/rails_root/config/initializers/inflections.rb +10 -0
  22. data/examples/rails_root/config/initializers/mime_types.rb +5 -0
  23. data/examples/rails_root/config/initializers/new_rails_defaults.rb +17 -0
  24. data/examples/rails_root/config/routes.rb +43 -0
  25. data/examples/rails_root/cucumber.yml +1 -0
  26. data/examples/rails_root/db/migrate/20090125013728_create_users.rb +11 -0
  27. data/examples/rails_root/db/schema.rb +19 -0
  28. data/examples/rails_root/doc/README_FOR_APP +5 -0
  29. data/examples/rails_root/features/errors.feature +42 -0
  30. data/examples/rails_root/features/example.feature +59 -0
  31. data/examples/rails_root/features/step_definitions/email_steps.rb +74 -0
  32. data/examples/rails_root/features/step_definitions/user_steps.rb +23 -0
  33. data/examples/rails_root/features/step_definitions/webrat_steps.rb +104 -0
  34. data/examples/rails_root/features/support/env.rb +21 -0
  35. data/examples/rails_root/public/404.html +30 -0
  36. data/examples/rails_root/public/422.html +30 -0
  37. data/examples/rails_root/public/500.html +33 -0
  38. data/examples/rails_root/public/dispatch.rb +10 -0
  39. data/examples/rails_root/public/favicon.ico +0 -0
  40. data/examples/rails_root/public/images/rails.png +0 -0
  41. data/examples/rails_root/public/javascripts/application.js +2 -0
  42. data/examples/rails_root/public/javascripts/controls.js +963 -0
  43. data/examples/rails_root/public/javascripts/dragdrop.js +973 -0
  44. data/examples/rails_root/public/javascripts/effects.js +1128 -0
  45. data/examples/rails_root/public/javascripts/prototype.js +4320 -0
  46. data/examples/rails_root/public/robots.txt +5 -0
  47. data/examples/rails_root/script/about +4 -0
  48. data/examples/rails_root/script/autospec +5 -0
  49. data/examples/rails_root/script/console +3 -0
  50. data/examples/rails_root/script/cucumber +7 -0
  51. data/examples/rails_root/script/dbconsole +3 -0
  52. data/examples/rails_root/script/destroy +3 -0
  53. data/examples/rails_root/script/generate +3 -0
  54. data/examples/rails_root/script/performance/benchmarker +3 -0
  55. data/examples/rails_root/script/performance/profiler +3 -0
  56. data/examples/rails_root/script/performance/request +3 -0
  57. data/examples/rails_root/script/plugin +3 -0
  58. data/examples/rails_root/script/process/inspector +3 -0
  59. data/examples/rails_root/script/process/reaper +3 -0
  60. data/examples/rails_root/script/process/spawner +3 -0
  61. data/examples/rails_root/script/runner +3 -0
  62. data/examples/rails_root/script/server +3 -0
  63. data/examples/rails_root/script/spec +5 -0
  64. data/examples/rails_root/script/spec_server +125 -0
  65. data/examples/rails_root/spec/controllers/welcome_controller_spec.rb +15 -0
  66. data/examples/rails_root/spec/model_factory.rb +6 -0
  67. data/examples/rails_root/spec/models/user_mailer_spec.rb +58 -0
  68. data/examples/rails_root/spec/models/user_spec.rb +5 -0
  69. data/examples/rails_root/spec/rcov.opts +2 -0
  70. data/examples/rails_root/spec/spec.opts +4 -0
  71. data/examples/rails_root/spec/spec_helper.rb +51 -0
  72. data/examples/rails_root/stories/all.rb +4 -0
  73. data/examples/rails_root/stories/helper.rb +3 -0
  74. data/examples/rails_root/vendor/plugins/email_spec/rails_generators/email_spec/email_spec_generator.rb +17 -0
  75. data/examples/rails_root/vendor/plugins/email_spec/rails_generators/email_spec/templates/email_steps.rb +74 -0
  76. data/install.rb +0 -0
  77. data/lib/email_spec.rb +14 -0
  78. data/lib/email_spec/address_converter.rb +27 -0
  79. data/lib/email_spec/cucumber.rb +19 -0
  80. data/lib/email_spec/deliveries.rb +55 -0
  81. data/lib/email_spec/email_viewer.rb +70 -0
  82. data/lib/email_spec/helpers.rb +128 -0
  83. data/lib/email_spec/matchers.rb +143 -0
  84. data/rails_generators/email_spec/email_spec_generator.rb +17 -0
  85. data/rails_generators/email_spec/templates/email_steps.rb +74 -0
  86. data/spec/email_spec/matchers_spec.rb +159 -0
  87. data/spec/spec.opts +4 -0
  88. data/spec/spec_helper.rb +4 -0
  89. metadata +179 -0
File without changes
@@ -0,0 +1,14 @@
1
+ unless defined?(ActionMailer)
2
+ Kernel.warn("ActionMailer did not appear to be loaded so email-spec is requiring it.")
3
+ require 'actionmailer'
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 'email_spec/deliveries'
9
+ require 'email_spec/address_converter'
10
+ require 'email_spec/email_viewer'
11
+ require 'email_spec/helpers'
12
+ require 'email_spec/matchers'
13
+
14
+
@@ -0,0 +1,27 @@
1
+ module EmailSpec
2
+ class AddressConverter
3
+ include Singleton
4
+
5
+ attr_accessor :converter
6
+
7
+ # The block provided to conversion should convert to an email
8
+ # address string or return the input untouched. For example:
9
+ #
10
+ # EmailSpec::AddressConverter.instance.conversion do |input|
11
+ # if input.is_a?(User)
12
+ # input.email
13
+ # else
14
+ # input
15
+ # end
16
+ # end
17
+ #
18
+ def conversion(&block)
19
+ self.converter = block
20
+ end
21
+
22
+ def convert(input)
23
+ return input unless converter
24
+ converter.call(input)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # require this in your env.rb file after you require cucumber/rails/world
2
+
3
+ # Global Setup
4
+ ActionMailer::Base.delivery_method = :test unless ActionMailer::Base.delivery_method == :activerecord
5
+ ActionMailer::Base.perform_deliveries = true
6
+
7
+ Before do
8
+ # Scenario setup
9
+ ActionMailer::Base.deliveries.clear if ActionMailer::Base.delivery_method == :test
10
+ end
11
+
12
+ After do
13
+ EmailSpec::EmailViewer.save_and_open_all_raw_emails if ENV['SHOW_EMAILS']
14
+ EmailSpec::EmailViewer.save_and_open_all_html_emails if ENV['SHOW_HTML_EMAILS']
15
+ EmailSpec::EmailViewer.save_and_open_all_text_emails if ENV['SHOW_TEXT_EMAILS']
16
+ end
17
+
18
+ World(EmailSpec::Helpers)
19
+ World(EmailSpec::Matchers)
@@ -0,0 +1,55 @@
1
+ module EmailSpec
2
+ module TestDeliveries
3
+ def all_emails
4
+ ActionMailer::Base.deliveries
5
+ end
6
+
7
+ def last_email_sent
8
+ ActionMailer::Base.deliveries.last || raise("No email has been sent!")
9
+ end
10
+
11
+ def reset_mailer
12
+ ActionMailer::Base.deliveries.clear
13
+ end
14
+
15
+ def mailbox_for(address)
16
+ address = AddressConverter.instance.convert(address)
17
+ ActionMailer::Base.deliveries.select { |m| m.to.include?(address) }
18
+ end
19
+ end
20
+
21
+ module ARMailerDeliveries
22
+ def all_emails
23
+ Email.all.map{ |email| parse_to_tmail(email) }
24
+ end
25
+
26
+ def last_email_sent
27
+ if email = Email.last
28
+ TMail::Mail.parse(email.mail)
29
+ else
30
+ raise("No email has been sent!")
31
+ end
32
+ end
33
+
34
+ def reset_mailer
35
+ Email.delete_all
36
+ end
37
+
38
+ def mailbox_for(address)
39
+ address = AddressConverter.instance.convert(address)
40
+ Email.all.select { |email| email.to.include?(address) }.map{ |email| parse_to_tmail(email) }
41
+ end
42
+
43
+ def parse_to_tmail(email)
44
+ TMail::Mail.parse(email.mail)
45
+ end
46
+ end
47
+
48
+ module Deliveries
49
+ if ActionMailer::Base.delivery_method == :activerecord
50
+ include EmailSpec::ARMailerDeliveries
51
+ else
52
+ include EmailSpec::TestDeliveries
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ module EmailSpec
2
+ class EmailViewer
3
+ extend Deliveries
4
+
5
+ def self.save_and_open_all_raw_emails
6
+ filename = "#{RAILS_ROOT}/tmp/email-#{Time.now.to_i}.txt"
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 = "#{RAILS_ROOT}/tmp/email-#{Time.now.to_i}.txt"
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.open_in_text_editor(filename)
59
+ `mate #{filename}`
60
+ end
61
+
62
+ def self.open_in_browser(filename)
63
+ `open #{filename}`
64
+ end
65
+
66
+ def self.tmp_email_filename(extension = '.txt')
67
+ "#{RAILS_ROOT}/tmp/email-#{Time.now.to_i}#{extension}"
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,128 @@
1
+ require 'uri'
2
+
3
+ module EmailSpec
4
+
5
+ module Helpers
6
+ include Deliveries
7
+
8
+ def visit_in_email(link_text)
9
+ visit(parse_email_for_link(current_email, link_text))
10
+ end
11
+
12
+ def click_email_link_matching(regex, email = current_email)
13
+ url = links_in_email(email).detect { |link| link =~ regex }
14
+ raise "No link found matching #{regex.inspect} in #{email.body}" unless url
15
+ request_uri = URI::parse(url).request_uri
16
+ visit request_uri
17
+ end
18
+
19
+ def click_first_link_in_email(email = current_email)
20
+ link = links_in_email(email).first
21
+ request_uri = URI::parse(link).request_uri
22
+ visit request_uri
23
+ end
24
+
25
+ def open_email(address, opts={})
26
+ address = convert_address(address)
27
+ set_current_email(find_email!(address, opts))
28
+ end
29
+
30
+ alias_method :open_email_for, :open_email
31
+
32
+ def open_last_email
33
+ set_current_email(last_email_sent)
34
+ end
35
+
36
+ def open_last_email_for(address)
37
+ address = convert_address(address)
38
+ set_current_email(mailbox_for(address).last)
39
+ end
40
+
41
+ def current_email(address=nil)
42
+ address = convert_address(address)
43
+ email = address ? email_spec_hash[:current_emails][address] : email_spec_hash[:current_email]
44
+ raise Spec::Expectations::ExpectationNotMetError, "Expected an open email but none was found. Did you forget to call open_email?" unless email
45
+ email
46
+ end
47
+
48
+ def unread_emails_for(address)
49
+ address = convert_address(address)
50
+ mailbox_for(address) - read_emails_for(address)
51
+ end
52
+
53
+ def read_emails_for(address)
54
+ address = convert_address(address)
55
+ email_spec_hash[:read_emails][address] ||= []
56
+ end
57
+
58
+ def find_email(address, opts={})
59
+ address = convert_address(address)
60
+ if opts[:with_subject]
61
+ email = mailbox_for(address).find { |m| m.subject =~ Regexp.new(opts[:with_subject]) }
62
+ elsif opts[:with_text]
63
+ email = mailbox_for(address).find { |m| m.body =~ Regexp.new(opts[:with_text]) }
64
+ else
65
+ email = mailbox_for(address).first
66
+ end
67
+ end
68
+
69
+ def links_in_email(email)
70
+ URI.extract(email.body, ['http', 'https'])
71
+ end
72
+
73
+ private
74
+
75
+ def email_spec_hash
76
+ @email_spec_hash ||= {:read_emails => {}, :unread_emails => {}, :current_emails => {}, :current_email => nil}
77
+ end
78
+
79
+ def find_email!(address, opts={})
80
+ address = convert_address(address)
81
+ email = find_email(address, opts)
82
+ if email.nil?
83
+ error = "#{opts.keys.first.to_s.humanize unless opts.empty?} #{('"' + opts.values.first.to_s.humanize + '"') unless opts.empty?}"
84
+ raise Spec::Expectations::ExpectationNotMetError, "Could not find email #{error}. \n Found the following emails:\n\n #{all_emails.to_s}"
85
+ end
86
+ email
87
+ end
88
+
89
+ def set_current_email(email)
90
+ return unless email
91
+ email.to.each do |to|
92
+ read_emails_for(to) << email
93
+ email_spec_hash[:current_emails][to] = email
94
+ end
95
+ email_spec_hash[:current_email] = email
96
+ end
97
+
98
+ def parse_email_for_link(email, text_or_regex)
99
+ email.should have_body_text(text_or_regex)
100
+
101
+ url = parse_email_for_explicit_link(email, text_or_regex)
102
+ url ||= parse_email_for_anchor_text_link(email, text_or_regex)
103
+
104
+ raise "No link found matching #{text_or_regex.inspect} in #{email}" unless url
105
+ url
106
+ end
107
+
108
+ # e.g. confirm in http://confirm
109
+ def parse_email_for_explicit_link(email, regex)
110
+ regex = /#{Regexp.escape(regex)}/ unless regex.is_a?(Regexp)
111
+ url = links_in_email(email).detect { |link| link =~ regex }
112
+ URI::parse(url).request_uri if url
113
+ end
114
+
115
+ # e.g. Click here in <a href="http://confirm">Click here</a>
116
+ def parse_email_for_anchor_text_link(email, link_text)
117
+ email.body =~ %r{<a[^>]*href=['"]?([^'"]*)['"]?[^>]*?>[^<]*?#{link_text}[^<]*?</a>}
118
+ URI.split($~[1])[5..-1].compact!.join("?").gsub("&amp;", "&")
119
+ # sub correct ampersand after rails switches it (http://dev.rubyonrails.org/ticket/4002)
120
+ end
121
+
122
+
123
+ def convert_address(address)
124
+ AddressConverter.instance.convert(address)
125
+ end
126
+ end
127
+ end
128
+
@@ -0,0 +1,143 @@
1
+ module EmailSpec
2
+
3
+ module Matchers
4
+
5
+ class DeliverTo
6
+
7
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
8
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
9
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
10
+ end
11
+
12
+ @expected_email_addresses = emails.sort
13
+ end
14
+
15
+ def description
16
+ "be delivered to #{@expected_email_addresses.inspect}"
17
+ end
18
+
19
+ def matches?(email)
20
+ @email = email
21
+ @actual_recipients = (email.to || []).sort
22
+ @actual_recipients == @expected_email_addresses
23
+ end
24
+
25
+ def failure_message
26
+ "expected #{@email.inspect} to deliver to #{@expected_email_addresses.inspect}, but it delievered to #{@actual_recipients.inspect}"
27
+ end
28
+
29
+ def negative_failure_message
30
+ "expected #{@email.inspect} not to deliver to #{@expected_email_addresses.inspect}, but it did"
31
+ end
32
+ end
33
+
34
+ def deliver_to(*expected_email_addresses_or_objects_that_respond_to_email)
35
+ DeliverTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
36
+ end
37
+
38
+ alias :be_delivered_to :deliver_to
39
+
40
+ custom_matcher :have_subject do |receiver, matcher, args|
41
+ given_subject = receiver.subject
42
+
43
+ # FIXME remove redundant variable definition
44
+ expected = args # args is an array
45
+
46
+ if expected.is_a?(String)
47
+ matcher.description = "have subject of #{expected.inspect}"
48
+ matcher.failure_message = "expected the subject to be #{expected.inspect} but was #{given_subject.inspect}"
49
+ matcher.negative_failure_message = "expected the subject not to be #{expected.inspect} but was"
50
+
51
+ given_subject == expected
52
+ else
53
+ matcher.description = "have subject matching #{expected.inspect}"
54
+ matcher.failure_message = "expected the subject to match #{expected.inspect}, but did not. Actual subject was: #{given_subject.inspect}"
55
+ matcher.negative_failure_message = "expected the subject not to match #{expected.inspect} but #{given_subject.inspect} does match it."
56
+
57
+ !!(given_subject =~ expected.first)
58
+ end
59
+ end
60
+
61
+ # def have_subject(expected)
62
+ # simple_matcher do |given, matcher|
63
+ # given_subject = given.subject
64
+ #
65
+ # if expected.is_a?(String)
66
+ # matcher.description = "have subject of #{expected.inspect}"
67
+ # matcher.failure_message = "expected the subject to be #{expected.inspect} but was #{given_subject.inspect}"
68
+ # matcher.negative_failure_message = "expected the subject not to be #{expected.inspect} but was"
69
+ #
70
+ # given_subject == expected
71
+ # else
72
+ # matcher.description = "have subject matching #{expected.inspect}"
73
+ # matcher.failure_message = "expected the subject to match #{expected.inspect}, but did not. Actual subject was: #{given_subject.inspect}"
74
+ # matcher.negative_failure_message = "expected the subject not to match #{expected.inspect} but #{given_subject.inspect} does match it."
75
+ #
76
+ # !!(given_subject =~ expected)
77
+ # end
78
+ # end
79
+ # end
80
+
81
+ def include_email_with_subject(expected)
82
+ simple_matcher do |given_emails, matcher|
83
+
84
+ if expected.is_a?(String)
85
+ matcher.description = "include email with subject of #{expected.inspect}"
86
+ matcher.failure_message = "expected at least one email to have the subject #{expected.inspect} but none did. Subjects were #{given_emails.map(&:subject).inspect}"
87
+ matcher.negative_failure_message = "expected no email with the subject #{expected.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}"
88
+
89
+ given_emails.map(&:subject).include?(expected)
90
+ else
91
+ matcher.description = "include email with subject matching #{expected.inspect}"
92
+ matcher.failure_message = "expected at least one email to have a subject matching #{expected.inspect}, but none did. Subjects were #{given_emails.map(&:subject).inspect}"
93
+ matcher.negative_failure_message = "expected no email to have a subject matching #{expected.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}"
94
+
95
+ !!(given_emails.any?{ |mail| mail.subject =~ expected })
96
+ end
97
+ end
98
+ end
99
+
100
+ def have_body_text(expected)
101
+ simple_matcher do |given, matcher|
102
+
103
+ if expected.is_a?(String)
104
+ normalized_body = given.body.gsub(/\s+/, " ")
105
+ normalized_expected = expected.gsub(/\s+/, " ")
106
+ matcher.description = "have body including #{normalized_expected.inspect}"
107
+ matcher.failure_message = "expected the body to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}"
108
+ matcher.negative_failure_message = "expected the body not to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}"
109
+
110
+ normalized_body.include?(normalized_expected)
111
+ else
112
+ given_body = given.body
113
+ matcher.description = "have body matching #{expected.inspect}"
114
+ matcher.failure_message = "expected the body to match #{expected.inspect}, but did not. Actual body was: #{given_body.inspect}"
115
+ matcher.negative_failure_message = "expected the body not to match #{expected.inspect} but #{given_body.inspect} does match it."
116
+
117
+ !!(given_body =~ expected)
118
+ end
119
+ end
120
+ end
121
+
122
+ def have_header(expected_name, expected_value)
123
+ simple_matcher do |given, matcher|
124
+ given_header = given.header
125
+
126
+ if expected_value.is_a?(String)
127
+ matcher.description = "have header #{expected_name}: #{expected_value}"
128
+ matcher.failure_message = "expected the headers to include '#{expected_name}: #{expected_value}' but they were #{given_header.inspect}"
129
+ matcher.negative_failure_message = "expected the headers not to include '#{expected_name}: #{expected_value}' but they were #{given_header.inspect}"
130
+
131
+ given_header[expected_name].to_s == expected_value
132
+ else
133
+ matcher.description = "have header #{expected_name} with value matching #{expected_value.inspect}"
134
+ matcher.failure_message = "expected the headers to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{given_header.inspect}"
135
+ matcher.negative_failure_message = "expected the headers not to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{given_header.inspect}"
136
+
137
+ given_header[expected_name].to_s =~ expected_value
138
+ end
139
+ end
140
+ end
141
+
142
+ end
143
+ end