riot_rails 0.0.8 → 0.0.9.pre
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/CHANGELOG +122 -0
- data/MIT-LICENSE +1 -1
- data/README.markdown +22 -7
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/lib/riot/action_controller/assertion_macros.rb +48 -34
- data/lib/riot/action_controller/context_helper.rb +12 -0
- data/lib/riot/action_controller/context_macros.rb +58 -12
- data/lib/riot/action_controller/situation_macros.rb +73 -6
- data/lib/riot/action_controller.rb +2 -0
- data/lib/riot/active_record/assertion_macros.rb +3 -158
- data/lib/riot/active_record/context_helper.rb +19 -0
- data/lib/riot/active_record/database_macros.rb +58 -0
- data/lib/riot/active_record/reflection_macros.rb +106 -0
- data/lib/riot/active_record/validation_macros.rb +187 -0
- data/lib/riot/active_record.rb +2 -0
- data/lib/riot/rails.rb +1 -2
- data/lib/riot/rails_context.rb +84 -0
- data/riot_rails.gemspec +35 -9
- data/test/action_controller/controller_context_test.rb +39 -9
- data/test/action_controller/redirected_to_test.rb +6 -11
- data/test/action_controller/renders_template_test.rb +7 -7
- data/test/action_controller/renders_test.rb +6 -6
- data/test/action_controller/response_status_test.rb +11 -11
- data/test/active_record/allowing_values_test.rb +9 -9
- data/test/active_record/attribute_is_invalid_test.rb +20 -0
- data/test/active_record/belongs_to_test.rb +22 -0
- data/test/active_record/has_and_belongs_to_many_test.rb +22 -0
- data/test/active_record/has_database_index_on_test.rb +73 -0
- data/test/active_record/has_many_test.rb +7 -3
- data/test/active_record/has_one_test.rb +22 -0
- data/test/active_record/validates_length_of_test.rb +31 -0
- data/test/active_record/validates_presence_of_test.rb +1 -1
- data/test/active_record/validates_uniqueness_of_test.rb +3 -3
- data/test/rails_context_test.rb +103 -0
- data/test/rails_root/config/environment.rb +46 -0
- data/test/rails_root/config/routes.rb +3 -4
- data/test/rails_root/db/schema.rb +1 -2
- data/test/teststrap.rb +44 -69
- metadata +39 -6
@@ -1,158 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
class AssertionMacro < ::Riot::AssertionMacro
|
5
|
-
def error_from_writing_value(model, attribute, value)
|
6
|
-
model.write_attribute(attribute, value)
|
7
|
-
model.valid?
|
8
|
-
model.errors.on(attribute)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
# An ActiveRecord assertion that expects to fail when a given attribute is validated after a nil value
|
13
|
-
# is provided to it.
|
14
|
-
#
|
15
|
-
# context "a User" do
|
16
|
-
# setup { User.new }
|
17
|
-
# topic.validates_presence_of(:name)
|
18
|
-
# end
|
19
|
-
class ValidatesPresenceOfMacro < AssertionMacro
|
20
|
-
register :validates_presence_of
|
21
|
-
|
22
|
-
def evaluate(actual, attribute)
|
23
|
-
if error_from_writing_value(actual, attribute, nil)
|
24
|
-
pass("validates presence of #{attribute.inspect}")
|
25
|
-
else
|
26
|
-
fail("expected to validate presence of #{attribute.inspect}")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# An ActiveRecord assertion that expects to pass with a given value or set of values for a given
|
32
|
-
# attribute.
|
33
|
-
#
|
34
|
-
# context "a User" do
|
35
|
-
# setup { User.new }
|
36
|
-
# topic.allows_values_for :email, "a@b.cd"
|
37
|
-
# topic.allows_values_for :email, "a@b.cd", "e@f.gh"
|
38
|
-
# end
|
39
|
-
class AllowsValuesForMacro < AssertionMacro
|
40
|
-
register :allows_values_for
|
41
|
-
|
42
|
-
def evaluate(actual, attribute, *values)
|
43
|
-
bad_values = []
|
44
|
-
values.each do |value|
|
45
|
-
bad_values << value if error_from_writing_value(actual, attribute, value)
|
46
|
-
end
|
47
|
-
failure_msg = "expected %s to allow value(s) %s"
|
48
|
-
bad_values.empty? ? pass : fail(failure_msg % [attribute.inspect, bad_values.inspect])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# An ActiveRecord assertion that expects to fail with a given value or set of values for a given
|
53
|
-
# attribute.
|
54
|
-
#
|
55
|
-
# context "a User" do
|
56
|
-
# setup { User.new }
|
57
|
-
# topic.does_not_allow_values_for :email, "a"
|
58
|
-
# topic.does_not_allow_values_for :email, "a@b", "e f@g.h"
|
59
|
-
# end
|
60
|
-
class DoesNotAllowValuesForMacro < AssertionMacro
|
61
|
-
register :does_not_allow_values_for
|
62
|
-
def evaluate(actual, attribute, *values)
|
63
|
-
good_values = []
|
64
|
-
values.each do |value|
|
65
|
-
good_values << value unless error_from_writing_value(actual, attribute, value)
|
66
|
-
end
|
67
|
-
failure_msg = "expected %s not to allow value(s) %s"
|
68
|
-
good_values.empty? ? pass : fail(failure_msg % [attribute.inspect, good_values.inspect])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# An ActiveRecord assertion that expects to fail with invalid value for an attribute. Optionally, the
|
73
|
-
# error message can be provided as the exact string or as regular expression.
|
74
|
-
#
|
75
|
-
# context "a User" do
|
76
|
-
# setup { User.new }
|
77
|
-
# topic.is_invalid_when :email, "fake", "is invalid"
|
78
|
-
# topic.is_invalid_when :email, "another fake", /invalid/
|
79
|
-
# end
|
80
|
-
class IsInvalidWhenMacro < AssertionMacro
|
81
|
-
register :is_invalid_when
|
82
|
-
|
83
|
-
def evaluate(actual, attribute, value, expected_error=nil)
|
84
|
-
actual_errors = Array(error_from_writing_value(actual, attribute, value))
|
85
|
-
if actual_errors.empty?
|
86
|
-
fail("expected #{attribute.inspect} to be invalid when value is #{value.inspect}")
|
87
|
-
elsif expected_error && !has_error_message?(expected_error, actual_errors)
|
88
|
-
message = "expected %s to be invalid with error message %s"
|
89
|
-
fail(message % [attribute.inspect, expected_error.inspect])
|
90
|
-
else
|
91
|
-
pass("attribute #{attribute.inspect} is invalid")
|
92
|
-
end
|
93
|
-
end
|
94
|
-
private
|
95
|
-
def has_error_message?(expected, errors)
|
96
|
-
return true unless expected
|
97
|
-
expected.kind_of?(Regexp) ? errors.any? {|e| e =~ expected } : errors.any? {|e| e == expected }
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# An ActiveRecord assertion that expects to fail with an attribute is not valid for record because the
|
102
|
-
# value of the attribute is not unique. Requires the topic of the context to be a created record; one
|
103
|
-
# that returns false for a call to +new_record?+.
|
104
|
-
#
|
105
|
-
# context "a User" do
|
106
|
-
# setup { User.create(:email => "a@b.cde", ... ) }
|
107
|
-
# topic.validates_uniqueness_of :email
|
108
|
-
# end
|
109
|
-
class ValidatesUniquenessOfMacro < AssertionMacro
|
110
|
-
register :validates_uniqueness_of
|
111
|
-
|
112
|
-
def evaluate(actual, attribute)
|
113
|
-
actual_record = actual
|
114
|
-
if actual_record.new_record?
|
115
|
-
fail("topic is not a new record when testing uniqueness of #{attribute.inspect}")
|
116
|
-
else
|
117
|
-
copied_model = actual_record.class.new
|
118
|
-
actual_record.attributes.each do |dup_attribute, dup_value|
|
119
|
-
copied_model.write_attribute(dup_attribute, dup_value)
|
120
|
-
end
|
121
|
-
copied_value = actual_record.read_attribute(attribute)
|
122
|
-
if error_from_writing_value(copied_model, attribute, copied_value)
|
123
|
-
pass("#{attribute.inspect} is unique")
|
124
|
-
else
|
125
|
-
fail("expected to fail because #{attribute.inspect} is not unique")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as +has_many+
|
132
|
-
# association. Will fail if an association is not defined for the attribute and if the association is
|
133
|
-
# not +has_many.jekyll
|
134
|
-
#
|
135
|
-
# context "a Room" do
|
136
|
-
# setup { Room.new }
|
137
|
-
#
|
138
|
-
# topic.has_many(:doors)
|
139
|
-
# topic.has_many(:floors) # should probably fail given our current universe :)
|
140
|
-
# end
|
141
|
-
class HasManyMacro < AssertionMacro
|
142
|
-
register :has_many
|
143
|
-
|
144
|
-
def evaluate(actual, attribute)
|
145
|
-
reflection = actual.class.reflect_on_association(attribute)
|
146
|
-
static_msg = "expected #{attribute.inspect} to be a has_many association, but was "
|
147
|
-
if reflection.nil?
|
148
|
-
fail(static_msg + "not")
|
149
|
-
elsif "has_many" != reflection.macro.to_s
|
150
|
-
fail(static_msg + "a #{reflection.macro} instead")
|
151
|
-
else
|
152
|
-
pass("has many #{attribute.inspect}")
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
end # ActiveRecord
|
158
|
-
end # RiotRails
|
1
|
+
require 'riot/active_record/validation_macros'
|
2
|
+
require 'riot/active_record/reflection_macros'
|
3
|
+
require 'riot/active_record/database_macros'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RiotRails
|
2
|
+
register_context_helper do
|
3
|
+
def prepare_context(context)
|
4
|
+
context.premium_setup { context.description.new } if active_record_context?(context)
|
5
|
+
|
6
|
+
context.transaction do |&original_block|
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
original_block.call
|
9
|
+
raise ::ActiveRecord::Rollback
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
private
|
14
|
+
def active_record_context?(context)
|
15
|
+
description = context.description
|
16
|
+
description.kind_of?(Class) && description.ancestors.include?(::ActiveRecord::Base)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end # RiotRails
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
# An ActiveRecord assertion macro that looks for an index on a given set of attributes in the table
|
5
|
+
# used by the model under test (aka: topic).
|
6
|
+
#
|
7
|
+
# asserts_topic.has_database_index_on :name
|
8
|
+
# asserts_topic.has_database_index_on :email, :group_name
|
9
|
+
#
|
10
|
+
# In the form used above, the assertion will pass if any index is found with the attributes listed
|
11
|
+
# (unique or not). To be specific about uniqueness, provide the +:unique+ option.
|
12
|
+
#
|
13
|
+
# asserts_topic.has_database_index_on :email, :unique => true
|
14
|
+
# asserts_topic.has_database_index_on :name, :unique => false
|
15
|
+
#
|
16
|
+
# The last example will require that the index not be a unique one.
|
17
|
+
class HasDatabaseIndexOnMacro < Riot::AssertionMacro
|
18
|
+
register :has_database_index_on
|
19
|
+
|
20
|
+
def initialize(database_connection=nil) # Good for testing :)
|
21
|
+
@database_connection = database_connection
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate(actual, *attributes_and_options)
|
25
|
+
attributes, options = extract_options_from(attributes_and_options)
|
26
|
+
unique = options[:unique]
|
27
|
+
|
28
|
+
index = find_index(actual, attributes, unique)
|
29
|
+
|
30
|
+
static_message = "#{unique ? "unique" : ""} index on #{attributes.inspect}".strip
|
31
|
+
index.nil? ? fail("expected #{static_message}") : pass("has #{static_message}")
|
32
|
+
end
|
33
|
+
private
|
34
|
+
def database_connection; @database_connection || ::ActiveRecord::Base.connection; end
|
35
|
+
|
36
|
+
def find_index(model, attributes, uniqueness, &block)
|
37
|
+
database_connection.indexes(model.class.table_name).find do |the_index|
|
38
|
+
unique_enough?(uniqueness, the_index) && attributes_match?(attributes, the_index)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unique_enough?(uniqueness, index)
|
43
|
+
return true if uniqueness.nil?
|
44
|
+
index.unique == uniqueness
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_match?(attributes, index)
|
48
|
+
Set.new(attributes.map(&:to_s)) == Set.new(index.columns)
|
49
|
+
end
|
50
|
+
|
51
|
+
def extract_options_from(attributes)
|
52
|
+
options = attributes.last.kind_of?(Hash) ? attributes.pop : {}
|
53
|
+
[attributes, options]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end # ActiveRecord
|
58
|
+
end # RiotRails
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
protected
|
4
|
+
|
5
|
+
class ReflectionAssertionMacro < Riot::AssertionMacro
|
6
|
+
private
|
7
|
+
def reflection_match?(expected, reflection)
|
8
|
+
!reflection.nil? && (expected == reflection.macro.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def options_match_for_reflection?(reflection, options)
|
12
|
+
options.all? { |k, v| reflection.options[k] == v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_reflection(expected, record, attribute, options=nil)
|
16
|
+
options ||= {}
|
17
|
+
reflection = record.class.reflect_on_association(attribute)
|
18
|
+
if reflection_match?(expected, reflection)
|
19
|
+
if options_match_for_reflection?(reflection, options)
|
20
|
+
pass new_message(attribute).is_a.push(expected).association
|
21
|
+
else
|
22
|
+
fail expected_message.push("#{expected} #{attribute.inspect}").with(options)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
fail new_message(attribute).is_not_a.push(expected).association
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
public
|
31
|
+
|
32
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a +has_many+
|
33
|
+
# association. Will fail if an association is not defined for the attribute or if the association is
|
34
|
+
# not +has_many+.
|
35
|
+
#
|
36
|
+
# context "a Room" do
|
37
|
+
# setup { Room.new }
|
38
|
+
#
|
39
|
+
# asserts_topic.has_many(:doors)
|
40
|
+
# asserts_topic.has_many(:floors) # should probably fail given our current universe :)
|
41
|
+
# end
|
42
|
+
class HasManyMacro < ReflectionAssertionMacro
|
43
|
+
register :has_many
|
44
|
+
|
45
|
+
def evaluate(actual, *expectings)
|
46
|
+
attribute, options = *expectings
|
47
|
+
assert_reflection("has_many", actual, attribute, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
52
|
+
# +has_one+ association. Will fail if an association is not defined for the attribute or if the
|
53
|
+
# association is not +has_one+.
|
54
|
+
#
|
55
|
+
# context "a Room" do
|
56
|
+
# setup { Room.new }
|
57
|
+
#
|
58
|
+
# asserts_topic.has_one(:floor)
|
59
|
+
# end
|
60
|
+
class HasOneMacro < ReflectionAssertionMacro
|
61
|
+
register :has_one
|
62
|
+
|
63
|
+
def evaluate(actual, *expectings)
|
64
|
+
attribute, options = *expectings
|
65
|
+
assert_reflection("has_one", actual, attribute, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
70
|
+
# +belongs_to+ association. Will fail if an association is not defined for the attribute or if the
|
71
|
+
# association is not +belongs_to+.
|
72
|
+
#
|
73
|
+
# context "a Room" do
|
74
|
+
# setup { Room.new }
|
75
|
+
#
|
76
|
+
# asserts_topic.belongs_to(:house)
|
77
|
+
# end
|
78
|
+
class BelongsToMacro < ReflectionAssertionMacro
|
79
|
+
register :belongs_to
|
80
|
+
|
81
|
+
def evaluate(actual, *expectings)
|
82
|
+
attribute, options = *expectings
|
83
|
+
assert_reflection("belongs_to", actual, attribute, options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# An ActiveRecord assertion macro that expects to pass when a given attribute is defined as a
|
88
|
+
# +has_and_belongs_to_many+ association. Will fail if an association is not defined for the attribute or
|
89
|
+
# if the association is not +has_and_belongs_to_many+.
|
90
|
+
#
|
91
|
+
# context "a Room" do
|
92
|
+
# setup { Room.new }
|
93
|
+
#
|
94
|
+
# asserts_topic.has_and_belongs_to_many(:walls)
|
95
|
+
# end
|
96
|
+
class HasAndBelongsToManyMacro < ReflectionAssertionMacro
|
97
|
+
register :has_and_belongs_to_many
|
98
|
+
|
99
|
+
def evaluate(actual, *expectings)
|
100
|
+
attribute, options = *expectings
|
101
|
+
assert_reflection("has_and_belongs_to_many", actual, attribute, options)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end # ActiveRecord
|
106
|
+
end # RiotRails
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module RiotRails
|
2
|
+
module ActiveRecord
|
3
|
+
protected
|
4
|
+
|
5
|
+
class ValidationAssertionMacro < Riot::AssertionMacro
|
6
|
+
private
|
7
|
+
def errors_from_writing_value(model, attribute, value)
|
8
|
+
# TODO: Fix me to not use __send__
|
9
|
+
model.__send__(:write_attribute, attribute, value)
|
10
|
+
model.valid?
|
11
|
+
model.errors[attribute]
|
12
|
+
end
|
13
|
+
|
14
|
+
def errors_from_writing_value?(*args)
|
15
|
+
errors_from_writing_value(*args).any?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
public
|
20
|
+
|
21
|
+
# An ActiveRecord assertion that expects to pass with a given value or set of values for a given
|
22
|
+
# attribute.
|
23
|
+
#
|
24
|
+
# rails_context User do
|
25
|
+
# asserts_topic.allows_values_for :email, "a@b.cd"
|
26
|
+
# asserts_topic.allows_values_for :email, "a@b.cd", "e@f.gh"
|
27
|
+
# end
|
28
|
+
class AllowsValuesForMacro < ValidationAssertionMacro
|
29
|
+
register :allows_values_for
|
30
|
+
|
31
|
+
def evaluate(actual, attribute, *values)
|
32
|
+
bad_values = []
|
33
|
+
values.each do |value|
|
34
|
+
bad_values << value if errors_from_writing_value?(actual, attribute, value)
|
35
|
+
end
|
36
|
+
bad_values.empty? ? pass : fail(expected_message(attribute).to_allow_values(bad_values))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# An ActiveRecord assertion that expects to fail with a given value or set of values for a given
|
41
|
+
# attribute.
|
42
|
+
#
|
43
|
+
# rails_context User do
|
44
|
+
# asserts_topic.does_not_allow_values_for :email, "a"
|
45
|
+
# asserts_topic.does_not_allow_values_for :email, "a@b", "e f@g.h"
|
46
|
+
# end
|
47
|
+
class DoesNotAllowValuesForMacro < ValidationAssertionMacro
|
48
|
+
register :does_not_allow_values_for
|
49
|
+
def evaluate(actual, attribute, *values)
|
50
|
+
good_values = []
|
51
|
+
values.each do |value|
|
52
|
+
good_values << value unless errors_from_writing_value?(actual, attribute, value)
|
53
|
+
end
|
54
|
+
good_values.empty? ? pass : fail(expected_message(attribute).not_to_allow_values(good_values))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# An ActiveRecord assertion that expects to fail with invalid value for an attribute. Optionally, the
|
59
|
+
# error message can be provided as the exact string or as regular expression.
|
60
|
+
#
|
61
|
+
# rails_context User do
|
62
|
+
# asserts_topic.is_invalid_when :email, "fake", "is invalid"
|
63
|
+
# asserts_topic.is_invalid_when :email, "another fake", /invalid/
|
64
|
+
# end
|
65
|
+
class IsInvalidWhenMacro < ValidationAssertionMacro
|
66
|
+
register :is_invalid_when
|
67
|
+
|
68
|
+
def evaluate(actual, attribute, value, expected_error=nil)
|
69
|
+
actual_errors = errors_from_writing_value(actual, attribute, value)
|
70
|
+
if actual_errors.empty?
|
71
|
+
fail expected_message(attribute).to_be_invalid_when_value_is(value)
|
72
|
+
elsif expected_error && !has_error_message?(expected_error, actual_errors)
|
73
|
+
fail expected_message(attribute).to_be_invalid_with_error_message(expected_error)
|
74
|
+
else
|
75
|
+
pass new_message.attribute(attribute).is_invalid
|
76
|
+
end
|
77
|
+
end
|
78
|
+
private
|
79
|
+
def has_error_message?(expected, errors)
|
80
|
+
return true unless expected
|
81
|
+
expected.kind_of?(Regexp) ? errors.any? {|e| e =~ expected } : errors.any? {|e| e == expected }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# An ActiveRecord assertion that expects to fail when a given attribute is validated after a nil value
|
86
|
+
# is provided to it.
|
87
|
+
#
|
88
|
+
# rails_context User do
|
89
|
+
# asserts_topic.validates_presence_of(:name)
|
90
|
+
# end
|
91
|
+
class ValidatesPresenceOfMacro < ValidationAssertionMacro
|
92
|
+
register :validates_presence_of
|
93
|
+
|
94
|
+
def evaluate(actual, attribute)
|
95
|
+
if errors_from_writing_value?(actual, attribute, nil)
|
96
|
+
pass new_message.validates_presence_of(attribute)
|
97
|
+
else
|
98
|
+
fail expected_message.to_validate_presence_of(attribute)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# An ActiveRecord assertion that expects to fail with an attribute is not valid for record because the
|
104
|
+
# value of the attribute is not unique. Requires the topic of the context to be a created record; one
|
105
|
+
# that returns false for a call to +new_record?+.
|
106
|
+
#
|
107
|
+
# rails_context User do
|
108
|
+
# setup { User.create(:email => "a@b.cde", ... ) }
|
109
|
+
# asserts_topic.validates_uniqueness_of :email
|
110
|
+
# end
|
111
|
+
class ValidatesUniquenessOfMacro < ValidationAssertionMacro
|
112
|
+
register :validates_uniqueness_of
|
113
|
+
|
114
|
+
def evaluate(actual, attribute)
|
115
|
+
actual_record = actual
|
116
|
+
if actual_record.new_record?
|
117
|
+
fail new_message.must_use_a_persisted_record_when_testing_uniqueness_of(attribute)
|
118
|
+
else
|
119
|
+
copied_model = actual_record.class.new
|
120
|
+
actual_record.attributes.each do |dup_attribute, dup_value|
|
121
|
+
copied_model.__send__(:write_attribute, dup_attribute, dup_value)
|
122
|
+
end
|
123
|
+
copied_value = actual_record.__send__(:read_attribute, attribute)
|
124
|
+
if errors_from_writing_value?(copied_model, attribute, copied_value)
|
125
|
+
pass new_message(attribute).is_unique
|
126
|
+
else
|
127
|
+
fail expected_message.to_fail_because(attribute).is_not_unique
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# An ActiveRecord assertion to test that the length of the value of an attribute must be within a
|
134
|
+
# specified range. Assertion fails if: there are errors when min/max characters are used, there are no
|
135
|
+
# errors when min-1/max+1 characters are used.
|
136
|
+
#
|
137
|
+
# rails_context User do
|
138
|
+
# asserts_topic.validates_length_of :name, (2..36)
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# TODO: allow for options on what to validate
|
142
|
+
class ValidatesLengthOfMacro < ValidationAssertionMacro
|
143
|
+
register :validates_length_of
|
144
|
+
|
145
|
+
def evaluate(actual, attribute, range)
|
146
|
+
min, max = range.first, range.last
|
147
|
+
[min, max].each do |length|
|
148
|
+
if errors_from_writing_value?(actual, attribute, "r" * length)
|
149
|
+
return fail(new_message(attribute).should_be_able_to_be(length).characters)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if (min-1) > 0 && !errors_from_writing_value?(actual, attribute, "r" * (min-1))
|
154
|
+
fail new_message(attribute).expected_to_be_more_than(min - 1).characters
|
155
|
+
elsif !errors_from_writing_value?(actual, attribute, "r" * (max+1))
|
156
|
+
fail new_message(attribute).expected_to_be_less_than(max + 1).characters
|
157
|
+
else
|
158
|
+
pass new_message.validates_length_of(attribute).is_within(range)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# An ActiveRecord assertion to test that an attribute is invalid along with the expected error message
|
164
|
+
# (or error message partial). The assumption is that a value has already been set.
|
165
|
+
#
|
166
|
+
# rails_context User do
|
167
|
+
# hookup { topic.bio = "I'm a goofy clown" }
|
168
|
+
# asserts_topic.attribute_is_invalid :bio, "cannot contain adjectives"
|
169
|
+
# end
|
170
|
+
class AttributeIsInvalidMacro < ValidationAssertionMacro
|
171
|
+
register :attribute_is_invalid
|
172
|
+
|
173
|
+
def evaluate(actual, attribute, error_message)
|
174
|
+
actual.valid?
|
175
|
+
errors = actual.errors[attribute]
|
176
|
+
if errors.empty?
|
177
|
+
fail new_message(attribute).expected_to_be_invalid
|
178
|
+
elsif errors.include?(error_message)
|
179
|
+
pass new_message(attribute).is_invalid
|
180
|
+
else
|
181
|
+
fail new_message(attribute).is_invalid.but(error_message).is_not_a_valid_error_message
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end # ActiveRecord
|
187
|
+
end # RiotRails
|
data/lib/riot/active_record.rb
CHANGED
data/lib/riot/rails.rb
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
module RiotRails
|
2
|
+
def self.helpers; @helpers ||= []; end
|
3
|
+
def self.register_context_helper(&handler_block)
|
4
|
+
helpers << Class.new(&handler_block).new
|
5
|
+
end
|
6
|
+
|
7
|
+
class RailsContext < Riot::Context
|
8
|
+
def initialize(description, parent=nil, &definition)
|
9
|
+
@options = {:transactional => false}
|
10
|
+
transaction { |&default_block| raise Exception, "No transaction handler" }
|
11
|
+
super(description, parent, &definition)
|
12
|
+
apply_helpers
|
13
|
+
end
|
14
|
+
|
15
|
+
# We're going to allow any helper to help out. Not just one.
|
16
|
+
def apply_helpers
|
17
|
+
RiotRails.helpers.each { |helper| helper.prepare_context(self) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set options for the specific rails_context. For instance, you can tell the context to be transactional
|
21
|
+
#
|
22
|
+
# rails_context "Foo" do
|
23
|
+
# set :transactional, true
|
24
|
+
# end
|
25
|
+
def set(property, value) options[property] = value; end
|
26
|
+
|
27
|
+
# Technically, this is a secret ... but whatever. It's ruby. Basically, just make this setup more
|
28
|
+
# important than any of the others.
|
29
|
+
#
|
30
|
+
# Yes I can. See ... I just did!
|
31
|
+
def premium_setup(&definition)
|
32
|
+
@setups.unshift(::Riot::Setup.new(&definition)).first;
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if current context or a parent context has the transactional option enabled. To enable,
|
36
|
+
# anywhere within a rails_context or a child context thereof, do:
|
37
|
+
#
|
38
|
+
# rails_context Foo do
|
39
|
+
# context "sub context" do
|
40
|
+
# set :transactional, true
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Transactional support is disabled by default. When enabled, all transactions are rolled back when the
|
45
|
+
# context is done executing.
|
46
|
+
def transactional?
|
47
|
+
options[:transactional] || (parent.respond_to?(:transactional?) && parent.transactional?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: cleanup how we handle transactions and context helpers
|
51
|
+
def transaction(&block) @transaction_block = block; end
|
52
|
+
|
53
|
+
def local_run(*args)
|
54
|
+
transactional? ? @transaction_block.call { super(*args) } : super
|
55
|
+
end
|
56
|
+
private
|
57
|
+
attr_reader :options
|
58
|
+
end
|
59
|
+
|
60
|
+
module Root
|
61
|
+
# Things an Object needs at the root level
|
62
|
+
#
|
63
|
+
# rails_context SomeKindOfClass do
|
64
|
+
# end
|
65
|
+
def rails_context(description, &definition)
|
66
|
+
context(description, RailsContext, &definition)
|
67
|
+
end
|
68
|
+
end # Root
|
69
|
+
|
70
|
+
module Context
|
71
|
+
# Things a running context needs
|
72
|
+
#
|
73
|
+
# context "Something" do
|
74
|
+
# rails_context SomeKindOfClass do
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
def rails_context(description, &definition)
|
78
|
+
new_context(description, RailsContext, &definition)
|
79
|
+
end
|
80
|
+
end # Context
|
81
|
+
end # RiotRails
|
82
|
+
|
83
|
+
Object.instance_eval { include RiotRails::Root }
|
84
|
+
Riot::Context.instance_eval { include RiotRails::Context }
|