schlick-active-matchers 0.3.1.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Pat Allan & James Healy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+
6
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
7
+
8
+ require 'active-matchers'
9
+
10
+ GEM = "active-matchers"
11
+ GEM_VERSION = ActiveMatchers::Version::STRING
12
+ AUTHOR = "Pat Allan"
13
+ EMAIL = "pat@freelancing-gods.com"
14
+ HOMEPAGE = "http://am.freelancing-gods.com"
15
+ SUMMARY = "Helpful rspec matchers for testing validations and associations."
16
+
17
+ spec = Gem::Specification.new do |s|
18
+ s.name = GEM
19
+ s.version = GEM_VERSION
20
+ s.platform = Gem::Platform::RUBY
21
+ s.rubyforge_project = GEM
22
+ s.has_rdoc = true
23
+ s.extra_rdoc_files = ['README.textile','LICENSE']
24
+ s.summary = SUMMARY
25
+ s.description = s.summary
26
+ s.author = AUTHOR
27
+ s.email = EMAIL
28
+ s.homepage = HOMEPAGE
29
+
30
+ s.add_dependency 'activerecord'
31
+
32
+ s.require_path = 'lib'
33
+ s.files = %w(README.textile LICENSE Rakefile init.rb) + Dir.glob("{lib,specs}/**/*")
34
+ end
35
+
36
+ Rake::GemPackageTask.new(spec) do |pkg|
37
+ pkg.gem_spec = spec
38
+ end
39
+
40
+ desc "install the gem locally"
41
+ task :install => [:package] do
42
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
43
+ end
44
+
45
+ desc "create a gemspec file"
46
+ task :make_spec do
47
+ File.open("#{GEM}.gemspec", "w") do |file|
48
+ file.puts spec.to_ruby
49
+ end
50
+ end
51
+
52
+ Rake::GemPackageTask.new(spec) do |p|
53
+ p.gem_spec = spec
54
+ p.need_tar = true
55
+ p.need_zip = true
56
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "active-matchers"
@@ -0,0 +1,20 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "active_record"
5
+ require "active-matchers/assoc_reflection_methods"
6
+ require "active-matchers/matchers"
7
+
8
+ ActiveRecord::Reflection::AssociationReflection.send(:include,
9
+ ActiveMatchers::AssociationReflectionMethods)
10
+
11
+ module ActiveMatchers
12
+ module Version #:nodoc:
13
+ MAJOR = 0
14
+ MINOR = 3
15
+ TINY = 1
16
+ MICRO = 1
17
+
18
+ STRING = [MAJOR, MINOR, TINY, MICRO].join('.').freeze
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveMatchers
2
+ module AssociationReflectionMethods
3
+ def to_hash
4
+ {
5
+ :macro => @macro,
6
+ :options => @options
7
+ }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,113 @@
1
+ require "active-matchers/matchers/association_matcher"
2
+ require "active-matchers/matchers/validation_matcher"
3
+ require "active-matchers/matchers/response_matchers"
4
+
5
+ module ActiveMatchers
6
+ module Matchers
7
+ # Test validates_presence_of :name
8
+ # Model.should need(:name).using(@valid_attributes)
9
+ #
10
+ # Test validates_uniqueness_of :name
11
+ # Model.should need(:name).to_be_unique.using(@valid_attributes)
12
+ #
13
+ # Test presence of at least one field being required
14
+ # Model.should need.one_of(:first_name, :last_name).using(@valid_attributes)
15
+ #
16
+ def need(*fields)
17
+ ValidationMatcher.new(:require, *fields).using(@base_attributes)
18
+ end
19
+
20
+ alias_method :mandate, :need
21
+
22
+ # Test validates_length_of :name matches database field length
23
+ # Model.should limit_length_of(:name).using(@valid_attributes)
24
+ #
25
+ # Test validates_length_of :name, :maximum => 255
26
+ # Model.should limit_length_of(:name).to(255).using(@valid_attributes)
27
+ #
28
+ def limit_length_of(*fields)
29
+ ValidationMatcher.new(:length, *fields).using(@base_attributes)
30
+ end
31
+
32
+ # Test belongs_to :parent
33
+ # Model.should belong_to(:parent)
34
+ #
35
+ # Test belongs_to :parent, :class_name => "CustomClass", :foreign_key => "some_id"
36
+ # Model.should belong_to(:parent).with_options(
37
+ # :class_name => "CustomClass", :foreign_key => "some_id")
38
+ #
39
+ def belong_to(*fields)
40
+ AssociationMatcher.new(:belongs_to, *fields)
41
+ end
42
+
43
+ # Test has_many :items
44
+ # Model.should have_many(:items)
45
+ #
46
+ # Test has_many :items, :class_name => "CustomClass", :foreign_key => "some_id"
47
+ # Model.should have_many(:items).with_options(
48
+ # :class_name => "CustomClass", :foreign_key => "some_id")
49
+ #
50
+ def have_many(*fields)
51
+ AssociationMatcher.new(:has_many, *fields)
52
+ end
53
+
54
+ # Test has_one :item
55
+ # Model.should have_one(:item)
56
+ #
57
+ # Test has_one :item, :class_name => "CustomClass", :foreign_key => "some_id"
58
+ # Model.should have_one(:item).with_options(
59
+ # :class_name => "CustomClass", :foreign_key => "some_id")
60
+ #
61
+ def have_one(*fields)
62
+ AssociationMatcher.new(:has_one, *fields)
63
+ end
64
+
65
+ # Test has_and_belongs_to_many :items
66
+ # Model.should have_and_belong_to_many(:items)
67
+ #
68
+ # Test has_and_belongs_to_many :items, :class_name => "CustomClass"
69
+ # Model.should have_one(:item).with_options(
70
+ # :class_name => "CustomClass")
71
+ #
72
+ def have_and_belong_to_many(*fields)
73
+ AssociationMatcher.new(:has_and_belongs_to_many, *fields)
74
+ end
75
+
76
+ # Useful for multiple requests using the same base attributes
77
+ #
78
+ # using(@valid_attributes) do
79
+ # Model.should need(:name)
80
+ # Model.should limit_length_of(:name).to(100)
81
+ # end
82
+ #
83
+ def using(base_attributes={}, &block)
84
+ @base_attributes = base_attributes
85
+ yield
86
+ @base_attributes= {}
87
+ end
88
+
89
+ #
90
+ def succeed
91
+ ResponseMatchers::SuccessMatcher.new(@controller)
92
+ end
93
+
94
+ # Use to confirm whether a response is/is not a 404
95
+ #
96
+ # response.should be_a_404
97
+ #
98
+ def be_a_404
99
+ ResponseMatchers::NotFoundMatcher.new
100
+ end
101
+
102
+ # Use to confirm whether a response redirected
103
+ #
104
+ # response.should redirect
105
+ # response.should_not redirect
106
+ # response.should redirect.to("url")
107
+ # response.should_not redirect.to("url")
108
+ #
109
+ def redirect
110
+ ResponseMatchers::RedirectMatcher.new
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,57 @@
1
+ module ActiveMatchers
2
+ module Matchers
3
+ class AssociationMatcher
4
+ def initialize(macro, *attributes)
5
+ @macro = macro
6
+ @attributes = attributes
7
+ end
8
+
9
+ def matches?(model)
10
+ @model = model
11
+ confirm_association
12
+ end
13
+
14
+ def failure_message
15
+ "Error: #{@error}"
16
+ end
17
+
18
+ def with_options(options)
19
+ @options = options
20
+ self
21
+ end
22
+
23
+ def through(assoc)
24
+ @options ||= {}
25
+ @options[:through] = assoc
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ def confirm_association
32
+ return if @attributes.empty?
33
+
34
+ @options ||= {}
35
+ @options[:extend] ||= []
36
+
37
+ @attributes.each do |attribute|
38
+ assoc = @model.reflect_on_association(attribute)
39
+ if assoc.nil?
40
+ @error = "#{@model.name} is missing the association #{attribute}"
41
+ return false
42
+ end
43
+ if assoc.to_hash[:macro] != @macro
44
+ @error = "#{@model.name}.#{attribute} should be #{@macro}, but is #{assoc.to_hash[:macro]}"
45
+ return false
46
+ end
47
+ if assoc.to_hash[:options] != @options
48
+ @error = "#{@model.name}.#{attribute} should have options #{@options.inspect}, but has options #{assoc.to_hash[:options].inspect}"
49
+ return false
50
+ end
51
+ end
52
+
53
+ true
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,105 @@
1
+ module ActiveMatchers
2
+ module Matchers
3
+ module ResponseMatchers
4
+ class SuccessMatcher
5
+ def initialize(controller)
6
+ @controller = controller
7
+ end
8
+
9
+ def matches?(response)
10
+ @response = response
11
+ if @template.nil?
12
+ @response.success?
13
+ else
14
+ @response.success? && full_path(@template) == full_path(@response.rendered_file)
15
+ end
16
+ end
17
+
18
+ def with_template(template)
19
+ @template = template
20
+ self
21
+ end
22
+
23
+ def failure_message
24
+ if @template.nil? || !@response.success?
25
+ "Response should have succeeded, but returned with code #{@response.response_code}."
26
+ else
27
+ "Response should have rendered #{@template}, but instead rendered #{@response.rendered_file}."
28
+ end
29
+ end
30
+
31
+ def negative_failure_message
32
+ if @response.success?
33
+ "Response should not have succeeded, but did."
34
+ else
35
+ "Response should not have rendered #{@template}, but did."
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def full_path(path)
42
+ return nil if path.nil?
43
+ path.include?('/') ? path : "#{@controller.class.to_s.underscore.gsub('_controller','')}/#{path}"
44
+ end
45
+ end
46
+
47
+ class NotFoundMatcher
48
+ def matches?(response)
49
+ @response = response
50
+ response.response_code == 404
51
+ end
52
+
53
+ def failure_message
54
+ "Response should have had a status of 404 Not Found, but had #{@response.response_code}."
55
+ end
56
+
57
+ def negative_failure_message
58
+ "Response should not have a status of 404 Not Found, but does."
59
+ end
60
+ end
61
+
62
+ class RedirectMatcher
63
+ def initialize
64
+ @action = :what
65
+ end
66
+
67
+ def matches?(response)
68
+ @response = response
69
+ case @action
70
+ when :what
71
+ @response.redirect?
72
+ when :where
73
+ @response.redirect? && @response.redirect_url == @url
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def to(url)
80
+ @action = :where
81
+ @url = url
82
+ self
83
+ end
84
+
85
+ def failure_message
86
+ case @action
87
+ when :what
88
+ "Response should have redirected, but didn't."
89
+ when :where
90
+ "Response should have redirected to #{@url}, but didn't."
91
+ end
92
+ end
93
+
94
+ def negative_failure_message
95
+ case @action
96
+ when :what
97
+ "Response shouldn't have redirected, but did."
98
+ when :where
99
+ "Response shouldn't have redirected to #{@url}, but did."
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,240 @@
1
+ module ActiveMatchers
2
+ module Matchers
3
+ class ValidationMatcher
4
+ def initialize(type, *attributes)
5
+ @type = type
6
+ @attributes = attributes
7
+ @create_action = 'create'
8
+ @new_action = 'new'
9
+ end
10
+
11
+ def matches?(model)
12
+ @model = model
13
+ case @type
14
+ when :require
15
+ confirm_required(&@if)
16
+ when :unique
17
+ confirm_unique
18
+ when :one_of_many
19
+ confirm_one_of_many
20
+ when :length
21
+ confirm_length
22
+ when :numeric
23
+ confirm_numericality
24
+ when :unsigned
25
+ confirm_zero_or_greater
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ def failure_message
32
+ "Error: #{@error}"
33
+ end
34
+
35
+ def to_be_unique
36
+ @type = :unique
37
+ self
38
+ end
39
+
40
+ def to_be_numeric
41
+ @type = :numeric
42
+ self
43
+ end
44
+
45
+ def to_be_unsigned
46
+ @type = :unsigned
47
+ self
48
+ end
49
+
50
+ def using(attributes={})
51
+ @base_attributes = attributes
52
+ self
53
+ end
54
+
55
+ def if(&block)
56
+ @if = block
57
+ self
58
+ end
59
+
60
+ def one_of(*attributes)
61
+ @attributes = attributes
62
+ @type = :one_of_many
63
+ self
64
+ end
65
+
66
+ def to(upper_limit)
67
+ @upper_limit = upper_limit
68
+ self
69
+ end
70
+
71
+ def from(lower_limit)
72
+ @lower_limit = lower_limit
73
+ self
74
+ end
75
+
76
+ # Override the assumed default 'create' action and allow another one to be used.
77
+ # This makes it possible to use an 'unsafe' create to bypass attr_accessible and friends.
78
+ def with_create(action)
79
+ @create_action = action
80
+ self
81
+ end
82
+
83
+ # Override the assumed default 'new' action and allow another one to be used.
84
+ # This makes it possible to use an 'unsafe' new to bypass attr_accessible and friends.
85
+ def with_new(action)
86
+ @new_action = action
87
+ self
88
+ end
89
+
90
+ private
91
+
92
+ def confirm_required
93
+ return true if @attributes.empty?
94
+
95
+ @attributes.each do |attribute|
96
+ obj = @model.send @new_action, @base_attributes.except(*attribute)
97
+ yield obj if block_given?
98
+
99
+ if obj.valid?
100
+ @error = "#{@model.name}.valid? should be false without #{attribute}, but returned true"
101
+ return false
102
+ end
103
+ if obj.errors.on(attribute).empty?
104
+ @error = "#{@model.name} should have errors on #{attribute} when #{attribute} is missing"
105
+ return false
106
+ end
107
+ obj.send "#{attribute.to_s}=", @base_attributes[attribute]
108
+ unless obj.valid?
109
+ @error = "#{@model.name} should be valid when #{attribute} is supplied"
110
+ return false
111
+ end
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ def confirm_unique
118
+ return true if @attributes.empty?
119
+
120
+ # Create first
121
+ @model.send @create_action, @base_attributes
122
+ # Create second, which will be invalid because unique values
123
+ # are duplicated
124
+ obj = @model.send @new_action, @base_attributes
125
+ if obj.valid?
126
+ @error = "#{@model.name} should not be valid when it is a duplicate"
127
+ return false
128
+ end
129
+ # Change the values of the unique attributes to remove collisions
130
+ @attributes.each do |attribute|
131
+ if obj.errors.on(attribute).empty?
132
+ @error = "#{@model.name} should have a value collision for #{attribute}"
133
+ return false
134
+ end
135
+ obj.send "#{attribute.to_s}=", "#{@base_attributes[attribute]} - Edit"
136
+ end
137
+ unless obj.valid?
138
+ @error = "#{@model.name} should be valid without duplicate values"
139
+ return false
140
+ end
141
+ true
142
+ end
143
+
144
+ def confirm_one_of_many
145
+ return true if @attributes.empty?
146
+
147
+ obj = @model.send @new_action, @base_attributes.except(*@attributes)
148
+ return false if obj.valid?
149
+ @attributes.each do |attribute|
150
+ obj.send "#{attribute.to_s}=", @base_attributes[attribute]
151
+ return false unless obj.valid?
152
+ obj.send "#{attribute.to_s}=", nil
153
+ return false if obj.valid?
154
+ end
155
+
156
+ true
157
+ end
158
+
159
+ def confirm_length
160
+ return true if @attributes.empty?
161
+
162
+ error_msgs = []
163
+ @lower_limit ||= 0
164
+
165
+ @attributes.each do |attribute|
166
+ obj = @model.send @new_action, @base_attributes.except(attribute)
167
+
168
+ if @lower_limit > 0
169
+ obj.send "#{attribute.to_s}=", 'a'*(@lower_limit)
170
+ error_msgs << "#{@model.name}.valid? should be true when #{attribute} has a length of #{@lower_limit}, but returned false" unless obj.valid?
171
+
172
+ obj.send "#{attribute.to_s}=", 'a'*(@lower_limit-1)
173
+ error_msgs << "#{@model.name}.valid? should be false when #{attribute} has a length less than #{@lower_limit}, but returned true" if obj.valid?
174
+ end
175
+
176
+ @upper_limit ||= @model.columns_hash[attribute.to_s].limit unless @lower_limit > 0
177
+
178
+ if @upper_limit
179
+ obj.send "#{attribute.to_s}=", 'a'*(@upper_limit)
180
+ error_msgs << "#{@model.name}.valid? should be true when #{attribute} has a length of #{@upper_limit}, but returned false" unless obj.valid?
181
+
182
+ obj.send "#{attribute.to_s}=", 'a'*(@upper_limit+1)
183
+ error_msgs << "#{@model.name}.valid? should be false when #{attribute} has a length greater than #{@upper_limit}, but returned true" if obj.valid?
184
+ end
185
+
186
+ unless error_msgs.empty?
187
+ @error = "#{@model.name} " + error_msgs.join(' and ')
188
+ return false
189
+ end
190
+ end
191
+
192
+ true
193
+ end
194
+
195
+ def confirm_numericality
196
+ return true if @attributes.empty?
197
+
198
+ obj = @model.send @new_action, @base_attributes
199
+
200
+ @attributes.each do |attribute|
201
+
202
+ unless obj.valid?
203
+ @error = "#{@model.name}.valid? should be true when #{attribute} is numeric, but returned false"
204
+ return false
205
+ end
206
+
207
+ # Change the attribute to a string
208
+ obj.send "#{attribute.to_s}=", "String"
209
+ if obj.valid?
210
+ @error = "#{@model.name}.valid? should be false when #{attribute} is not numeric, but returned true"
211
+ return false
212
+ end
213
+
214
+ obj.send "#{attribute.to_s}=", @base_attributes[attribute]
215
+ end
216
+
217
+ true
218
+ end
219
+
220
+ def confirm_zero_or_greater
221
+ return true if @attributes.empty?
222
+
223
+ obj = @model.send @new_action, @base_attributes
224
+
225
+ @attributes.each do |attribute|
226
+ obj.send("#{attribute.to_s}=",-1)
227
+ if obj.valid?
228
+ @error = "#{@model.name}.valid? should be false when #{attribute} is less than zero, but returned true"
229
+ return false
230
+ end
231
+
232
+ obj.send "#{attribute.to_s}=", @base_attributes[attribute]
233
+ end
234
+
235
+ true
236
+ end
237
+
238
+ end
239
+ end
240
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schlick-active-matchers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ autorequire: active-matchers
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-02 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: Helpful rspec matchers for testing validations and associations.
25
+ email: pat@freelancing-gods.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ files:
34
+ - README
35
+ - LICENSE
36
+ - Rakefile
37
+ - init.rb
38
+ - lib/active-matchers
39
+ - lib/active-matchers/assoc_reflection_methods.rb
40
+ - lib/active-matchers/matchers
41
+ - lib/active-matchers/matchers/association_matcher.rb
42
+ - lib/active-matchers/matchers/response_matchers.rb
43
+ - lib/active-matchers/matchers/validation_matcher.rb
44
+ - lib/active-matchers/matchers.rb
45
+ - lib/active-matchers.rb
46
+ has_rdoc: true
47
+ homepage: http://am.freelancing-gods.com
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: Helpful rspec matchers for testing validations and associations.
72
+ test_files: []
73
+