francois-shoulda 2.0.5.4 → 2.10.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.
Files changed (109) hide show
  1. data/README.rdoc +60 -10
  2. data/Rakefile +7 -7
  3. data/lib/shoulda.rb +7 -15
  4. data/lib/shoulda/action_controller.rb +28 -0
  5. data/lib/shoulda/action_controller/helpers.rb +47 -0
  6. data/lib/shoulda/action_controller/macros.rb +277 -0
  7. data/lib/shoulda/action_controller/matchers.rb +37 -0
  8. data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
  9. data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
  10. data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
  11. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +70 -0
  12. data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
  13. data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
  14. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
  15. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
  16. data/lib/shoulda/action_mailer.rb +1 -1
  17. data/lib/shoulda/action_mailer/assertions.rb +32 -33
  18. data/lib/shoulda/action_view.rb +10 -0
  19. data/lib/shoulda/action_view/macros.rb +56 -0
  20. data/lib/shoulda/active_record.rb +6 -2
  21. data/lib/shoulda/active_record/assertions.rb +62 -89
  22. data/lib/shoulda/active_record/helpers.rb +40 -0
  23. data/lib/shoulda/active_record/macros.rb +520 -684
  24. data/lib/shoulda/active_record/matchers.rb +42 -0
  25. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  26. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  27. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  28. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  29. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  30. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  31. data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
  32. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
  33. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  34. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  35. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  36. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  37. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  38. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  39. data/lib/shoulda/assertions.rb +50 -40
  40. data/lib/shoulda/autoload_macros.rb +46 -0
  41. data/lib/shoulda/context.rb +124 -126
  42. data/lib/shoulda/helpers.rb +5 -7
  43. data/lib/shoulda/macros.rb +63 -64
  44. data/lib/shoulda/private_helpers.rb +16 -18
  45. data/lib/shoulda/rails.rb +5 -11
  46. data/lib/shoulda/rspec.rb +11 -0
  47. data/lib/shoulda/tasks/list_tests.rake +6 -1
  48. data/lib/shoulda/test_unit.rb +19 -0
  49. data/rails/init.rb +7 -1
  50. data/test/README +2 -2
  51. data/test/fail_macros.rb +15 -15
  52. data/test/fixtures/tags.yml +1 -1
  53. data/test/functional/posts_controller_test.rb +46 -26
  54. data/test/functional/users_controller_test.rb +0 -19
  55. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
  56. data/test/matchers/active_record/allow_value_matcher_test.rb +41 -0
  57. data/test/matchers/active_record/association_matcher_test.rb +258 -0
  58. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
  59. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  60. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  61. data/test/matchers/active_record/have_index_matcher_test.rb +74 -0
  62. data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
  63. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  64. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  65. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  66. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  67. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  68. data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
  69. data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
  70. data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
  71. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +27 -0
  72. data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
  73. data/test/matchers/controller/route_matcher_test.rb +58 -0
  74. data/test/matchers/controller/set_session_matcher_test.rb +31 -0
  75. data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
  76. data/test/model_builder.rb +106 -0
  77. data/test/other/autoload_macro_test.rb +18 -0
  78. data/test/other/helpers_test.rb +58 -0
  79. data/test/other/private_helpers_test.rb +1 -1
  80. data/test/other/should_test.rb +16 -16
  81. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  82. data/test/rails_root/app/models/pets/dog.rb +10 -0
  83. data/test/rails_root/app/models/treat.rb +3 -0
  84. data/test/rails_root/app/models/user.rb +4 -3
  85. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  86. data/test/rails_root/config/database.yml +1 -1
  87. data/test/rails_root/config/environment.rb +1 -1
  88. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  89. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  90. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  91. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  92. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  93. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  94. data/test/rspec_test.rb +207 -0
  95. data/test/test_helper.rb +3 -1
  96. data/test/unit/address_test.rb +1 -23
  97. data/test/unit/dog_test.rb +5 -2
  98. data/test/unit/post_test.rb +7 -3
  99. data/test/unit/product_test.rb +2 -2
  100. data/test/unit/tag_test.rb +2 -1
  101. data/test/unit/user_test.rb +25 -9
  102. metadata +84 -23
  103. data/lib/shoulda/controller.rb +0 -30
  104. data/lib/shoulda/controller/formats/html.rb +0 -201
  105. data/lib/shoulda/controller/formats/xml.rb +0 -170
  106. data/lib/shoulda/controller/helpers.rb +0 -64
  107. data/lib/shoulda/controller/macros.rb +0 -316
  108. data/lib/shoulda/controller/resource_options.rb +0 -236
  109. data/test/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,42 @@
1
+ require 'shoulda/active_record/helpers'
2
+ require 'shoulda/active_record/matchers/validation_matcher'
3
+ require 'shoulda/active_record/matchers/allow_value_matcher'
4
+ require 'shoulda/active_record/matchers/ensure_length_of_matcher'
5
+ require 'shoulda/active_record/matchers/ensure_inclusion_of_matcher'
6
+ require 'shoulda/active_record/matchers/validate_presence_of_matcher'
7
+ require 'shoulda/active_record/matchers/validate_uniqueness_of_matcher'
8
+ require 'shoulda/active_record/matchers/validate_acceptance_of_matcher'
9
+ require 'shoulda/active_record/matchers/validate_numericality_of_matcher'
10
+ require 'shoulda/active_record/matchers/association_matcher'
11
+ require 'shoulda/active_record/matchers/have_db_column_matcher'
12
+ require 'shoulda/active_record/matchers/have_index_matcher'
13
+ require 'shoulda/active_record/matchers/have_readonly_attribute_matcher'
14
+ require 'shoulda/active_record/matchers/allow_mass_assignment_of_matcher'
15
+ require 'shoulda/active_record/matchers/have_named_scope_matcher'
16
+
17
+
18
+ module Shoulda # :nodoc:
19
+ module ActiveRecord # :nodoc:
20
+ # = Matchers for your active record models
21
+ #
22
+ # These matchers will test most of the validations and associations for your
23
+ # ActiveRecord models.
24
+ #
25
+ # describe User do
26
+ # it { should validate_presence_of(:name) }
27
+ # it { should validate_presence_of(:phone_number) }
28
+ # %w(abcd 1234).each do |value|
29
+ # it { should_not allow_value(value).for(:phone_number) }
30
+ # end
31
+ # it { should allow_value("(123) 456-7890").for(:phone_number) }
32
+ # it { should_not allow_mass_assignment_of(:password) }
33
+ # it { should have_one(:profile) }
34
+ # it { should have_many(:dogs) }
35
+ # it { should have_many(:messes).through(:dogs) }
36
+ # it { should belong_to(:lover) }
37
+ # end
38
+ #
39
+ module Matchers
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,83 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the attribute can be set on mass update.
6
+ #
7
+ # it { should_not allow_mass_assignment_of(:password) }
8
+ # it { should allow_mass_assignment_of(:first_name) }
9
+ #
10
+ def allow_mass_assignment_of(value)
11
+ AllowMassAssignmentOfMatcher.new(value)
12
+ end
13
+
14
+ class AllowMassAssignmentOfMatcher # :nodoc:
15
+
16
+ def initialize(attribute)
17
+ @attribute = attribute.to_s
18
+ end
19
+
20
+ def matches?(subject)
21
+ @subject = subject
22
+ if attr_mass_assignable?
23
+ if whitelisting?
24
+ @failure_message = "#{@attribute} was made accessible"
25
+ else
26
+ if protected_attributes.empty?
27
+ @failure_message = "no attributes were protected"
28
+ else
29
+ @failure_message = "#{class_name} is protecting " <<
30
+ "#{protected_attributes.to_a.to_sentence}, " <<
31
+ "but not #{@attribute}."
32
+ end
33
+ end
34
+ true
35
+ else
36
+ if whitelisting?
37
+ @negative_failure_message =
38
+ "Expected #{@attribute} to be accessible"
39
+ else
40
+ @negative_failure_message =
41
+ "Did not expect #{@attribute} to be protected"
42
+ end
43
+ false
44
+ end
45
+ end
46
+
47
+ attr_reader :failure_message, :negative_failure_message
48
+
49
+ def description
50
+ "allow mass assignment of #{@attribute}"
51
+ end
52
+
53
+ private
54
+
55
+ def protected_attributes
56
+ @protected_attributes ||= (@subject.class.protected_attributes || [])
57
+ end
58
+
59
+ def accessible_attributes
60
+ @accessible_attributes ||= (@subject.class.accessible_attributes || [])
61
+ end
62
+
63
+ def whitelisting?
64
+ !accessible_attributes.empty?
65
+ end
66
+
67
+ def attr_mass_assignable?
68
+ if whitelisting?
69
+ accessible_attributes.include?(@attribute)
70
+ else
71
+ !protected_attributes.include?(@attribute)
72
+ end
73
+ end
74
+
75
+ def class_name
76
+ @subject.class.name
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,102 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the attribute can be set to the given value.
6
+ #
7
+ # Options:
8
+ # * <tt>with_message</tt> - value the test expects to find in
9
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
10
+ # translation for :invalid.
11
+ #
12
+ # Example:
13
+ # it { should_not allow_value('bad').for(:isbn) }
14
+ # it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
15
+ #
16
+ def allow_value(value)
17
+ AllowValueMatcher.new(value)
18
+ end
19
+
20
+ class AllowValueMatcher # :nodoc:
21
+ include Helpers
22
+
23
+ def initialize(value)
24
+ @value = value
25
+ end
26
+
27
+ def for(attribute)
28
+ @attribute = attribute
29
+ self
30
+ end
31
+
32
+ def with_message(message)
33
+ @expected_message = message if message
34
+ self
35
+ end
36
+
37
+ def matches?(instance)
38
+ @instance = instance
39
+ @expected_message ||= :invalid
40
+ if Symbol === @expected_message
41
+ @expected_message = default_error_message(@expected_message)
42
+ end
43
+ @instance.send("#{@attribute}=", @value)
44
+ !errors_match?
45
+ end
46
+
47
+ def failure_message
48
+ "Did not expect #{expectation}, got error: #{@matched_error}"
49
+ end
50
+
51
+ def negative_failure_message
52
+ "Expected #{expectation}, got #{error_description}"
53
+ end
54
+
55
+ def description
56
+ "allow #{@attribute} to be set to #{@value.inspect}"
57
+ end
58
+
59
+ private
60
+
61
+ def errors_match?
62
+ @instance.valid?
63
+ @errors = @instance.errors.on(@attribute)
64
+ @errors = [@errors] unless @errors.is_a?(Array)
65
+ errors_match_regexp? || errors_match_string?
66
+ end
67
+
68
+ def errors_match_regexp?
69
+ if Regexp === @expected_message
70
+ @matched_error = @errors.detect { |e| e =~ @expected_message }
71
+ !@matched_error.nil?
72
+ else
73
+ false
74
+ end
75
+ end
76
+
77
+ def errors_match_string?
78
+ if @errors.include?(@expected_message)
79
+ @matched_error = @expected_message
80
+ true
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ def expectation
87
+ "errors to include #{@expected_message.inspect} " <<
88
+ "when #{@attribute} is set to #{@value.inspect}"
89
+ end
90
+
91
+ def error_description
92
+ if @instance.errors.empty?
93
+ "no errors"
94
+ else
95
+ "errors: #{pretty_error_messages(@instance)}"
96
+ end
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,226 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure that the belongs_to relationship exists.
6
+ #
7
+ # it { should belong_to(:parent) }
8
+ #
9
+ def belong_to(name)
10
+ AssociationMatcher.new(:belongs_to, name)
11
+ end
12
+
13
+ # Ensures that the has_many relationship exists. Will also test that the
14
+ # associated table has the required columns. Works with polymorphic
15
+ # associations.
16
+ #
17
+ # Options:
18
+ # * <tt>through</tt> - association name for <tt>has_many :through</tt>
19
+ # * <tt>dependent</tt> - tests that the association makes use of the
20
+ # dependent option.
21
+ #
22
+ # Example:
23
+ # it { should_have_many(:friends) }
24
+ # it { should_have_many(:enemies).through(:friends) }
25
+ # it { should_have_many(:enemies).dependent(:destroy) }
26
+ #
27
+ def have_many(name)
28
+ AssociationMatcher.new(:has_many, name)
29
+ end
30
+
31
+ # Ensure that the has_one relationship exists. Will also test that the
32
+ # associated table has the required columns. Works with polymorphic
33
+ # associations.
34
+ #
35
+ # Options:
36
+ # * <tt>:dependent</tt> - tests that the association makes use of the
37
+ # dependent option.
38
+ #
39
+ # Example:
40
+ # it { should have_one(:god) } # unless hindu
41
+ #
42
+ def have_one(name)
43
+ AssociationMatcher.new(:has_one, name)
44
+ end
45
+
46
+ # Ensures that the has_and_belongs_to_many relationship exists, and that
47
+ # the join table is in place.
48
+ #
49
+ # it { should have_and_belong_to_many(:posts) }
50
+ #
51
+ def have_and_belong_to_many(name)
52
+ AssociationMatcher.new(:has_and_belongs_to_many, name)
53
+ end
54
+
55
+ class AssociationMatcher # :nodoc:
56
+ def initialize(macro, name)
57
+ @macro = macro
58
+ @name = name
59
+ end
60
+
61
+ def through(through)
62
+ @through = through
63
+ self
64
+ end
65
+
66
+ def dependent(dependent)
67
+ @dependent = dependent
68
+ self
69
+ end
70
+
71
+ def matches?(subject)
72
+ @subject = subject
73
+ association_exists? &&
74
+ macro_correct? &&
75
+ foreign_key_exists? &&
76
+ through_association_valid? &&
77
+ dependent_correct? &&
78
+ join_table_exists?
79
+ end
80
+
81
+ def failure_message
82
+ "Expected #{expectation} (#{@missing})"
83
+ end
84
+
85
+ def negative_failure_message
86
+ "Did not expect #{expectation}"
87
+ end
88
+
89
+ def description
90
+ description = "#{macro_description} #{@name}"
91
+ description += " through #{@through}" if @through
92
+ description += " dependent => #{@dependent}" if @dependent
93
+ description
94
+ end
95
+
96
+ protected
97
+
98
+ def association_exists?
99
+ if reflection.nil?
100
+ @missing = "no association called #{@name}"
101
+ false
102
+ else
103
+ true
104
+ end
105
+ end
106
+
107
+ def macro_correct?
108
+ if reflection.macro == @macro
109
+ true
110
+ else
111
+ @missing = "actual association type was #{reflection.macro}"
112
+ false
113
+ end
114
+ end
115
+
116
+ def foreign_key_exists?
117
+ !(belongs_foreign_key_missing? || has_foreign_key_missing?)
118
+ end
119
+
120
+ def belongs_foreign_key_missing?
121
+ @macro == :belongs_to && !class_has_foreign_key?(model_class)
122
+ end
123
+
124
+ def has_foreign_key_missing?
125
+ [:has_many, :has_one].include?(@macro) &&
126
+ !through? &&
127
+ !class_has_foreign_key?(associated_class)
128
+ end
129
+
130
+ def through_association_valid?
131
+ @through.nil? || (through_association_exists? && through_association_correct?)
132
+ end
133
+
134
+ def through_association_exists?
135
+ if through_reflection.nil?
136
+ "#{model_class.name} does not have any relationship to #{@through}"
137
+ false
138
+ else
139
+ true
140
+ end
141
+ end
142
+
143
+ def through_association_correct?
144
+ if @through == reflection.options[:through]
145
+ "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
146
+ " but got it through #{reflection.options[:through]}"
147
+ true
148
+ else
149
+ false
150
+ end
151
+ end
152
+
153
+ def dependent_correct?
154
+ if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s
155
+ true
156
+ else
157
+ @missing = "#{@name} should have #{@dependent} dependency"
158
+ false
159
+ end
160
+ end
161
+
162
+ def join_table_exists?
163
+ if @macro != :has_and_belongs_to_many ||
164
+ ::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
165
+ true
166
+ else
167
+ @missing = "join table #{join_table} doesn't exist"
168
+ false
169
+ end
170
+ end
171
+
172
+ def class_has_foreign_key?(klass)
173
+ if klass.column_names.include?(foreign_key.to_s)
174
+ true
175
+ else
176
+ @missing = "#{klass} does not have a #{foreign_key} foreign key."
177
+ false
178
+ end
179
+ end
180
+
181
+ def model_class
182
+ @subject.class
183
+ end
184
+
185
+ def join_table
186
+ reflection.options[:join_table]
187
+ end
188
+
189
+ def associated_class
190
+ reflection.klass
191
+ end
192
+
193
+ def foreign_key
194
+ reflection.primary_key_name
195
+ end
196
+
197
+ def through?
198
+ reflection.options[:through]
199
+ end
200
+
201
+ def reflection
202
+ @reflection ||= model_class.reflect_on_association(@name)
203
+ end
204
+
205
+ def through_reflection
206
+ @through_reflection ||= model_class.reflect_on_association(@through)
207
+ end
208
+
209
+ def expectation
210
+ "#{model_class.name} to have a #{@macro} association called #{@name}"
211
+ end
212
+
213
+ def macro_description
214
+ case @macro.to_s
215
+ when 'belongs_to' then 'belong to'
216
+ when 'has_many' then 'have many'
217
+ when 'has_one' then 'have one'
218
+ when 'has_and_belongs_to_many' then
219
+ 'have and belong to many'
220
+ end
221
+ end
222
+ end
223
+
224
+ end
225
+ end
226
+ end