mongoid-spec 4.0.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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +373 -0
- data/Rakefile +17 -0
- data/lib/matchers/accept_nested_attributes.rb +67 -0
- data/lib/matchers/allow_mass_assignment.rb +102 -0
- data/lib/matchers/associations.rb +330 -0
- data/lib/matchers/be_dynamic_document.rb +26 -0
- data/lib/matchers/be_mongoid_document.rb +26 -0
- data/lib/matchers/be_stored_in.rb +50 -0
- data/lib/matchers/have_field.rb +90 -0
- data/lib/matchers/have_index_for.rb +63 -0
- data/lib/matchers/have_timestamps.rb +61 -0
- data/lib/matchers/validations.rb +81 -0
- data/lib/matchers/validations/acceptance_of.rb +9 -0
- data/lib/matchers/validations/associated.rb +19 -0
- data/lib/matchers/validations/confirmation_of.rb +22 -0
- data/lib/matchers/validations/custom_validation_of.rb +47 -0
- data/lib/matchers/validations/exclusion_of.rb +49 -0
- data/lib/matchers/validations/format_of.rb +71 -0
- data/lib/matchers/validations/inclusion_of.rb +49 -0
- data/lib/matchers/validations/length_of.rb +147 -0
- data/lib/matchers/validations/numericality_of.rb +90 -0
- data/lib/matchers/validations/presence_of.rb +9 -0
- data/lib/matchers/validations/uniqueness_of.rb +82 -0
- data/lib/matchers/validations/with_message.rb +27 -0
- data/lib/mongoid-rspec.rb +1 -0
- data/lib/mongoid/rspec.rb +40 -0
- data/lib/mongoid/rspec/version.rb +5 -0
- data/spec/models/article.rb +29 -0
- data/spec/models/comment.rb +6 -0
- data/spec/models/log.rb +9 -0
- data/spec/models/movie_article.rb +8 -0
- data/spec/models/permalink.rb +5 -0
- data/spec/models/person.rb +10 -0
- data/spec/models/profile.rb +16 -0
- data/spec/models/record.rb +5 -0
- data/spec/models/site.rb +9 -0
- data/spec/models/user.rb +37 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/unit/accept_nested_attributes_spec.rb +12 -0
- data/spec/unit/associations_spec.rb +42 -0
- data/spec/unit/be_dynamic_document_spec.rb +22 -0
- data/spec/unit/be_mongoid_document_spec.rb +25 -0
- data/spec/unit/be_stored_in.rb +54 -0
- data/spec/unit/document_spec.rb +16 -0
- data/spec/unit/have_index_for_spec.rb +46 -0
- data/spec/unit/have_timestamps_spec.rb +71 -0
- data/spec/unit/validations_spec.rb +54 -0
- data/spec/validators/ssn_validator.rb +16 -0
- metadata +171 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
def be_mongoid_document
|
4
|
+
BeMongoidDocument.new
|
5
|
+
end
|
6
|
+
|
7
|
+
class BeMongoidDocument
|
8
|
+
def matches?(actual)
|
9
|
+
@model = actual.is_a?(Class) ? actual : actual.class
|
10
|
+
@model.included_modules.include?(Mongoid::Document)
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
'include Mongoid::Document'
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message
|
18
|
+
"expect #{@model.inspect} class to #{description}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def failure_message_when_negated
|
22
|
+
"expect #{@model.inspect} class to not #{description}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
def be_stored_in(options)
|
4
|
+
BeStoredIn.new(options)
|
5
|
+
end
|
6
|
+
|
7
|
+
class BeStoredIn
|
8
|
+
def initialize(expected)
|
9
|
+
@expected_options = \
|
10
|
+
expected
|
11
|
+
.transform_values { |v| v.to_sym rescue v }
|
12
|
+
.symbolize_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(actual)
|
16
|
+
@model = actual.is_a?(Class) ? actual : actual.class
|
17
|
+
actual_options == @expected_options
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
"be stored in #{@expected_options.inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message
|
25
|
+
"Expected #{@model.inspect} to #{description}, got #{actual_options.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message_when_negated
|
29
|
+
"Expected #{@model.inspect} not to #{description}, got #{actual_options.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def actual_options
|
35
|
+
@actual_options ||= begin
|
36
|
+
hash = @model.storage_options.slice(*@expected_options.keys)
|
37
|
+
hash.each do |option, value|
|
38
|
+
hash[option] =
|
39
|
+
if value.is_a?(Proc)
|
40
|
+
evaluated_value = @model.persistence_context.send("#{option}_name")
|
41
|
+
evaluated_value.to_sym rescue evaluated_value
|
42
|
+
else
|
43
|
+
value.to_sym rescue value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
class HaveField # :nodoc:
|
4
|
+
def initialize(*attrs)
|
5
|
+
@attributes = attrs.collect(&:to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def localized
|
9
|
+
@localized = true
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def of_type(type)
|
14
|
+
@type = type
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_alias(field_alias)
|
19
|
+
@field_alias = field_alias
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_default_value_of(default)
|
24
|
+
@default = default
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def matches?(klass)
|
29
|
+
@klass = klass.is_a?(Class) ? klass : klass.class
|
30
|
+
@errors = []
|
31
|
+
@attributes.each do |attr|
|
32
|
+
if @klass.fields.include?(attr)
|
33
|
+
error = ""
|
34
|
+
if @type and @klass.fields[attr].type != @type
|
35
|
+
error << " of type #{@klass.fields[attr].type}"
|
36
|
+
end
|
37
|
+
|
38
|
+
if !@default.nil?
|
39
|
+
if @klass.fields[attr].default_val.nil?
|
40
|
+
error << " with default not set"
|
41
|
+
elsif @klass.fields[attr].default_val != @default
|
42
|
+
error << " with default value of #{@klass.fields[attr].default_val}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if @field_alias and @klass.fields[attr].options[:as] != @field_alias
|
47
|
+
error << " with alias #{@klass.fields[attr].options[:as]}"
|
48
|
+
end
|
49
|
+
|
50
|
+
@errors.push("field #{attr.inspect}" << error) unless error.blank?
|
51
|
+
|
52
|
+
if @localized
|
53
|
+
unless @klass.fields[attr].localized?
|
54
|
+
@errors.push "is not localized #{attr.inspect}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
else
|
59
|
+
@errors.push "no field named #{attr.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
@errors.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message_for_should
|
66
|
+
"Expected #{@klass.inspect} to #{description}, got #{@errors.to_sentence}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def failure_message_for_should_not
|
70
|
+
"Expected #{@klass.inspect} to not #{description}, got #{@klass.inspect} to #{description}"
|
71
|
+
end
|
72
|
+
|
73
|
+
alias :failure_message :failure_message_for_should
|
74
|
+
alias :failure_message_when_negated :failure_message_for_should_not
|
75
|
+
|
76
|
+
def description
|
77
|
+
desc = "have #{@attributes.size > 1 ? 'fields' : 'field'} named #{@attributes.collect(&:inspect).to_sentence}"
|
78
|
+
desc << " of type #{@type.inspect}" if @type
|
79
|
+
desc << " with alias #{@field_alias}" if @field_alias
|
80
|
+
desc << " with default value of #{@default.inspect}" if !@default.nil?
|
81
|
+
desc
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def have_field(*args)
|
86
|
+
HaveField.new(*args)
|
87
|
+
end
|
88
|
+
alias_method :have_fields, :have_field
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
def have_index_for(index_key)
|
4
|
+
HaveIndexFor.new(index_key)
|
5
|
+
end
|
6
|
+
|
7
|
+
class HaveIndexFor
|
8
|
+
def initialize(index_key)
|
9
|
+
@index_key = index_key.symbolize_keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_options(index_options)
|
13
|
+
@index_options = index_options
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(actual)
|
18
|
+
@model = actual.is_a?(Class) ? actual : actual.class
|
19
|
+
|
20
|
+
actual_index &&
|
21
|
+
expected_index.key == actual_index.key &&
|
22
|
+
expected_index.fields == actual_index.fields &&
|
23
|
+
(expected_index.options.to_a - actual_index.options.to_a).empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure_message
|
27
|
+
message = "Expected #{@model.inspect} to #{description},"
|
28
|
+
if actual_index.nil?
|
29
|
+
message << " found no index"
|
30
|
+
else
|
31
|
+
message << " got #{index_description(actual_index)}"
|
32
|
+
end
|
33
|
+
message
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure_message_when_negated
|
37
|
+
"Expected #{@model.inspect} to not #{description}, got #{index_description(actual_index)}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def description
|
41
|
+
"have an index #{index_description(expected_index)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def index_description(index)
|
47
|
+
desc = "#{index.key.inspect}"
|
48
|
+
desc << " for fields #{index.fields.inspect}" if index.fields.present?
|
49
|
+
desc << " including options #{index.options.inspect}" if index.options.present?
|
50
|
+
desc
|
51
|
+
end
|
52
|
+
|
53
|
+
def expected_index
|
54
|
+
@expected_index ||=
|
55
|
+
Mongoid::Indexable::Specification.new(@model, @index_key, @index_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def actual_index
|
59
|
+
@actual_index ||= @model.index_specification(expected_index.key)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
def have_timestamps
|
4
|
+
HaveTimestamps.new
|
5
|
+
end
|
6
|
+
|
7
|
+
class HaveTimestamps
|
8
|
+
def initialize
|
9
|
+
@root_module = 'Mongoid::Timestamps'
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(actual)
|
13
|
+
@model = actual.is_a?(Class) ? actual : actual.class
|
14
|
+
@model.included_modules.include?(expected_module)
|
15
|
+
end
|
16
|
+
|
17
|
+
def for(phase)
|
18
|
+
fail('You\'ve already declared timetamp\'s sub-module via "for" clause') if @submodule
|
19
|
+
|
20
|
+
case @phase = phase.to_sym
|
21
|
+
when :creating then @submodule = 'Created'
|
22
|
+
when :updating then @submodule = 'Updated'
|
23
|
+
else
|
24
|
+
fail('Timestamps can be declared only for creating or updating')
|
25
|
+
end
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def shortened
|
31
|
+
@shortened = true
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
desc = "be a Mongoid document with"
|
37
|
+
desc << " shorted" if @shortened
|
38
|
+
desc << " #{@phase}" if @phase
|
39
|
+
desc << " timestamps"
|
40
|
+
desc
|
41
|
+
end
|
42
|
+
|
43
|
+
def failure_message
|
44
|
+
"Expected #{@model.inspect} class to #{description}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def failure_message_when_negated
|
48
|
+
"Expected #{@model.inspect} class to not #{description}"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def expected_module
|
54
|
+
expected_module = @root_module
|
55
|
+
expected_module << "::#{@submodule}" if @submodule
|
56
|
+
expected_module << "::Short" if @shortened
|
57
|
+
expected_module.constantize
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
|
5
|
+
class HaveValidationMatcher
|
6
|
+
|
7
|
+
def initialize(field, validation_type)
|
8
|
+
@field = field.to_s
|
9
|
+
@type = validation_type.to_s
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(actual)
|
14
|
+
@klass = actual.is_a?(Class) ? actual : actual.class
|
15
|
+
|
16
|
+
@validator = @klass.validators_on(@field).detect{ |v|
|
17
|
+
v.kind.to_s == @type and (!v.options[:on] or on_options_matches?(v))
|
18
|
+
}
|
19
|
+
|
20
|
+
if @validator
|
21
|
+
@negative_result_message = "#{@type.inspect} validator on #{@field.inspect}"
|
22
|
+
@positive_result_message = "#{@type.inspect} validator on #{@field.inspect}"
|
23
|
+
else
|
24
|
+
@negative_result_message = "no #{@type.inspect} validator on #{@field.inspect}"
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
@result = true
|
28
|
+
check_on if @options[:on]
|
29
|
+
@result
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure_message_for_should
|
33
|
+
"Expected #{@klass.inspect} to #{description}; instead got #{@negative_result_message}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure_message_for_should_not
|
37
|
+
"Expected #{@klass.inspect} to not #{description}; instead got #{@positive_result_message}"
|
38
|
+
end
|
39
|
+
|
40
|
+
alias :failure_message :failure_message_for_should
|
41
|
+
alias :failure_message_when_negated :failure_message_for_should_not
|
42
|
+
|
43
|
+
def description
|
44
|
+
desc = "have #{@type.inspect} validator on #{@field.inspect}"
|
45
|
+
desc << " on #{@options[:on]}" if @options[:on]
|
46
|
+
desc
|
47
|
+
end
|
48
|
+
|
49
|
+
def on(*on_method)
|
50
|
+
@options[:on] = on_method.flatten
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_on
|
55
|
+
validator_on_methods = [@validator.options[:on]].flatten
|
56
|
+
|
57
|
+
if validator_on_methods.any?
|
58
|
+
message = " on methods: #{validator_on_methods}"
|
59
|
+
|
60
|
+
if on_options_covered_by?( @validator )
|
61
|
+
@positive_result_message << message
|
62
|
+
else
|
63
|
+
@negative_result_message << message
|
64
|
+
@result = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def on_options_matches?(validator)
|
72
|
+
@options[:on] and validator.options[:on] and on_options_covered_by?(validator)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_options_covered_by?(validator)
|
76
|
+
([@options[:on]].flatten - [validator.options[:on]].flatten).empty?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
class ValidateAssociatedMatcher < HaveValidationMatcher
|
5
|
+
def initialize(name)
|
6
|
+
super(name, :associated)
|
7
|
+
end
|
8
|
+
|
9
|
+
def description
|
10
|
+
"validate associated #{@field.inspect}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_associated(association_name)
|
15
|
+
ValidateAssociatedMatcher.new(association_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
class ValidateConfirmationOfMatcher < HaveValidationMatcher
|
5
|
+
include WithMessage
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
super(name, :confirmation)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_message(message)
|
12
|
+
@expected_message = message
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_confirmation_of(field)
|
18
|
+
ValidateConfirmationOfMatcher.new(field)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Matchers
|
3
|
+
module Validations
|
4
|
+
class ValidateWithCustomValidatorMatcher < HaveValidationMatcher
|
5
|
+
include WithMessage
|
6
|
+
def initialize(field)
|
7
|
+
super(field, :custom)
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_validator(custom_validator)
|
11
|
+
@custom_validator = custom_validator
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(actual)
|
16
|
+
return false unless (@result = super(actual))
|
17
|
+
check_custom_validator if @custom_validator
|
18
|
+
check_expected_message if @expected_message
|
19
|
+
|
20
|
+
@result
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
options_desc = []
|
25
|
+
options_desc << " with custom validator #{@custom_validator.name}" if @validator
|
26
|
+
options_desc << " with message '#{@expected_message}'" if @expected_message
|
27
|
+
"validate field #{@field.inspect}" << options_desc.to_sentence
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def check_custom_validator
|
33
|
+
if @validator.kind_of? @custom_validator
|
34
|
+
@positive_result_message << " with custom validator of type #{@custom_validator.name}"
|
35
|
+
else
|
36
|
+
@negative_result_message << " with custom validator not of type #{@custom_validator.name}"
|
37
|
+
@result = false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def custom_validate(field)
|
43
|
+
ValidateWithCustomValidatorMatcher.new(field)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|