spec/grape/validations_spec.rb in grape-0.9.0 vs spec/grape/validations_spec.rb in grape-0.10.0
- old
+ new
@@ -1,9 +1,8 @@
require 'spec_helper'
describe Grape::Validations do
-
subject { Class.new(Grape::API) }
def app
subject
end
@@ -42,22 +41,21 @@
it 'adds to declared parameters' do
subject.params do
optional :some_param
end
- expect(subject.settings[:declared_params]).to eq([:some_param])
+ expect(subject.route_setting(:declared_params)).to eq([:some_param])
end
end
context 'required' do
before do
subject.params do
- requires :key
+ requires :key, type: String
end
- subject.get '/required' do
- 'required works'
- end
+ subject.get('/required') { 'required works' }
+ subject.put('/required') { { key: params[:key] }.to_json }
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
@@ -72,12 +70,18 @@
it 'adds to declared parameters' do
subject.params do
requires :some_param
end
- expect(subject.settings[:declared_params]).to eq([:some_param])
+ expect(subject.route_setting(:declared_params)).to eq([:some_param])
end
+
+ it 'works when required field is present but nil' do
+ put '/required', { key: nil }.to_json, 'CONTENT_TYPE' => 'application/json'
+ expect(last_response.status).to eq(200)
+ expect(JSON.parse(last_response.body)).to eq('key' => nil)
+ end
end
context 'requires :all using Grape::Entity documentation' do
def define_requires_all
documentation = {
@@ -95,11 +99,11 @@
end
end
it 'adds entity documentation to declared params' do
define_requires_all
- expect(subject.settings[:declared_params]).to eq([:required_field, :optional_field])
+ expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
it 'errors when required_field is not present' do
get '/required'
expect(last_response.status).to eq(400)
@@ -130,11 +134,11 @@
end
end
it 'adds entity documentation to declared params' do
define_requires_none
- expect(subject.settings[:declared_params]).to eq([:required_field, :optional_field])
+ expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
it 'errors when required_field is not present' do
get '/required'
expect(last_response.status).to eq(400)
@@ -160,11 +164,11 @@
end
end
it 'adds only the entity documentation to declared params, nothing more' do
define_requires_all
- expect(subject.settings[:declared_params]).to eq([:required_field, :optional_field])
+ expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
end
context 'requires :none' do
def define_requires_none
@@ -188,23 +192,22 @@
subject.params do
requires :items, type: Array do
requires :key
end
end
- subject.get '/required' do
- 'required works'
- end
+ subject.get('/required') { 'required works' }
+ subject.put('/required') { { items: params[:items] }.to_json }
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is missing')
end
- it "errors when param is not an Array" do
- get '/required', items: "hello"
+ it 'errors when param is not an Array' do
+ get '/required', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/required', items: { key: 'foo' }
expect(last_response.status).to eq(400)
@@ -215,27 +218,23 @@
get '/required', items: [{ key: 'hello' }, { key: 'world' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
- it "doesn't allow any key in the options hash other than type" do
- expect {
- subject.params do
- requires(:items, desc: 'Foo') do
- requires :key
- end
- end
- }.to raise_error ArgumentError
+ it "doesn't throw a missing param when param is present but empty" do
+ put '/required', { items: [] }.to_json, 'CONTENT_TYPE' => 'application/json'
+ expect(last_response.status).to eq(200)
+ expect(JSON.parse(last_response.body)).to eq('items' => [])
end
it 'adds to declared parameters' do
subject.params do
requires :items do
requires :key
end
end
- expect(subject.settings[:declared_params]).to eq([items: [:key]])
+ expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'required with a Hash block' do
before do
@@ -253,12 +252,12 @@
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is missing, items[key] is missing')
end
- it "errors when param is not a Hash" do
- get '/required', items: "hello"
+ it 'errors when param is not a Hash' do
+ get '/required', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/required', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
@@ -269,27 +268,17 @@
get '/required', items: { key: 'hello' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
- it "doesn't allow any key in the options hash other than type" do
- expect {
- subject.params do
- requires(:items, desc: 'Foo') do
- requires :key
- end
- end
- }.to raise_error ArgumentError
- end
-
it 'adds to declared parameters' do
subject.params do
requires :items do
requires :key
end
end
- expect(subject.settings[:declared_params]).to eq([items: [:key]])
+ expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'group' do
before do
@@ -319,14 +308,92 @@
subject.params do
group :items do
requires :key
end
end
- expect(subject.settings[:declared_params]).to eq([items: [:key]])
+ expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
+ context 'group params with nested params which has a type' do
+ let(:invalid_items){ { items: '' } }
+
+ before do
+ subject.params do
+ optional :items do
+ optional :key1, type: String
+ optional :key2, type: String
+ end
+ end
+ subject.post '/group_with_nested' do
+ 'group with nested works'
+ end
+ end
+
+ it 'errors when group param is invalid'do
+ post '/group_with_nested', items: invalid_items
+ expect(last_response.status).to eq(400)
+ end
+ end
+
+ context 'custom validator for a Hash' do
+ module DateRangeValidations
+ class DateRangeValidator < Grape::Validations::Base
+ def validate_param!(attr_name, params)
+ unless params[attr_name][:from] <= params[attr_name][:to]
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'"
+ end
+ end
+ end
+ end
+
+ before do
+ subject.params do
+ optional :date_range, date_range: true, type: Hash do
+ requires :from, type: Integer
+ requires :to, type: Integer
+ end
+ end
+ subject.get('/optional') do
+ 'optional works'
+ end
+ subject.params do
+ requires :date_range, date_range: true, type: Hash do
+ requires :from, type: Integer
+ requires :to, type: Integer
+ end
+ end
+ subject.get('/required') do
+ 'required works'
+ end
+ end
+
+ context 'which is optional' do
+ it "doesn't throw an error if the validation passes" do
+ get '/optional', date_range: { from: 1, to: 2 }
+ expect(last_response.status).to eq(200)
+ end
+
+ it 'errors if the validation fails' do
+ get '/optional', date_range: { from: 2, to: 1 }
+ expect(last_response.status).to eq(400)
+ end
+ end
+
+ context 'which is required' do
+ it "doesn't throw an error if the validation passes" do
+ get '/required', date_range: { from: 1, to: 2 }
+ expect(last_response.status).to eq(200)
+ end
+
+ it 'errors if the validation fails' do
+ get '/required', date_range: { from: 2, to: 1 }
+ expect(last_response.status).to eq(400)
+ end
+ end
+ end
+
context 'validation within arrays' do
before do
subject.params do
group :children do
requires :name
@@ -369,13 +436,13 @@
get '/within_array', children: [name: 'Jay']
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
end
- it "errors when param is not an Array" do
+ it 'errors when param is not an Array' do
# NOTE: would be nicer if these just returned 'children is invalid'
- get '/within_array', children: "hello"
+ get '/within_array', children: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children is invalid, children[name] is missing, children[parents] is missing, children[parents] is invalid, children[parents][name] is missing')
get '/within_array', children: { name: 'foo' }
expect(last_response.status).to eq(400)
@@ -426,11 +493,11 @@
''
end
end
it 'requires defaults to Array type' do
- get '/req', planets: "Jupiter, Saturn"
+ get '/req', planets: 'Jupiter, Saturn'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('planets is invalid, planets[name] is missing')
get '/req', planets: { name: 'Jupiter' }
expect(last_response.status).to eq(400)
@@ -442,30 +509,30 @@
put_with_json '/req', planets: []
expect(last_response.status).to eq(200)
end
it 'optional defaults to Array type' do
- get '/opt', name: "Jupiter", moons: "Europa, Ganymede"
+ get '/opt', name: 'Jupiter', moons: 'Europa, Ganymede'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('moons is invalid, moons[name] is missing')
- get '/opt', name: "Jupiter", moons: { name: 'Ganymede' }
+ get '/opt', name: 'Jupiter', moons: { name: 'Ganymede' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('moons is invalid')
- get '/opt', name: "Jupiter", moons: [{ name: 'Io' }, { name: 'Callisto' }]
+ get '/opt', name: 'Jupiter', moons: [{ name: 'Io' }, { name: 'Callisto' }]
expect(last_response.status).to eq(200)
- put_with_json '/opt', name: "Venus"
+ put_with_json '/opt', name: 'Venus'
expect(last_response.status).to eq(200)
- put_with_json '/opt', name: "Mercury", moons: []
+ put_with_json '/opt', name: 'Mercury', moons: []
expect(last_response.status).to eq(200)
end
it 'group defaults to Array type' do
- get '/grp', stars: "Sun"
+ get '/grp', stars: 'Sun'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('stars is invalid, stars[name] is missing')
get '/grp', stars: { name: 'Sun' }
expect(last_response.status).to eq(400)
@@ -543,18 +610,18 @@
get '/optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional group works')
end
- it "errors when group is present, but required param is not" do
+ it 'errors when group is present, but required param is not' do
get '/optional_group', items: [{ not_key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[key] is missing')
end
it "errors when param is present but isn't an Array" do
- get '/optional_group', items: "hello"
+ get '/optional_group', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/optional_group', items: { key: 'foo' }
expect(last_response.status).to eq(400)
@@ -565,11 +632,11 @@
subject.params do
optional :items do
requires :key
end
end
- expect(subject.settings[:declared_params]).to eq([items: [:key]])
+ expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'nested optional Array blocks' do
before do
@@ -629,11 +696,11 @@
requires :key
optional(:optional_subitems) { requires :value }
requires(:required_subitems) { requires :value }
end
end
- expect(subject.settings[:declared_params]).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
+ expect(subject.route_setting(:declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
end
end
context 'multiple validation errors' do
before do
@@ -654,14 +721,14 @@
end
end
context 'custom validation' do
module CustomValidations
- class Customvalidator < Grape::Validations::Validator
+ class Customvalidator < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name] == 'im custom'
- raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "is not custom!"
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'is not custom!'
end
end
end
end
@@ -774,17 +841,17 @@
get '/nested/one', custom: 'im wrong, validate me'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
- specify 'the nested namesapce inherits the custom validator' do
+ specify 'the nested namespace inherits the custom validator' do
get '/nested/nested/two', custom: 'im wrong, validate me'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
- specify 'peer namesapces does not have the validator' do
+ specify 'peer namespaces does not have the validator' do
get '/peer/one', custom: 'im not validated'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('no validation required')
end
@@ -803,22 +870,22 @@
end
end
context 'when using options on param' do
module CustomValidations
- class CustomvalidatorWithOptions < Grape::Validations::SingleOptionValidator
+ class CustomvalidatorWithOptions < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name] == @option[:text]
- raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: @option[:error_message]
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: @option[:error_message]
end
end
end
end
before do
subject.params do
- optional :custom, customvalidator_with_options: { text: 'im custom with options', error_message: "is not custom with options!" }
+ optional :custom, customvalidator_with_options: { text: 'im custom with options', error_message: 'is not custom with options!' }
end
subject.get '/optional_custom' do
'optional with custom works!'
end
end
@@ -875,20 +942,19 @@
it 'by #use' do
subject.params do
use :pagination
end
- expect(subject.settings[:declared_params]).to eq [:page, :per_page]
+ expect(subject.route_setting(:declared_params)).to eq [:page, :per_page]
end
it 'by #use with multiple params' do
subject.params do
use :pagination, :period
end
- expect(subject.settings[:declared_params]).to eq [:page, :per_page, :start_date, :end_date]
+ expect(subject.route_setting(:declared_params)).to eq [:page, :per_page, :start_date, :end_date]
end
-
end
context 'with block' do
before do
subject.helpers do
@@ -958,33 +1024,78 @@
'mutually_exclusive works!'
end
get '/mutually_exclusive', beer: 'string', wine: 'anotherstring'
expect(last_response.status).to eq(400)
- expect(last_response.body).to eq "beer, wine are mutually exclusive"
+ expect(last_response.body).to eq 'beer, wine are mutually exclusive'
end
end
context 'more than one set of mutually exclusive params' do
it 'errors for all sets' do
subject.params do
optional :beer
optional :wine
mutually_exclusive :beer, :wine
- optional :scotch
- optional :aquavit
- mutually_exclusive :scotch, :aquavit
+ optional :nested, type: Hash do
+ optional :scotch
+ optional :aquavit
+ mutually_exclusive :scotch, :aquavit
+ end
+ optional :nested2, type: Array do
+ optional :scotch2
+ optional :aquavit2
+ mutually_exclusive :scotch2, :aquavit2
+ end
end
subject.get '/mutually_exclusive' do
'mutually_exclusive works!'
end
- get '/mutually_exclusive', beer: 'true', wine: 'true', scotch: 'true', aquavit: 'true'
+ get '/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]
expect(last_response.status).to eq(400)
- expect(last_response.body).to eq "beer, wine are mutually exclusive, scotch, aquavit are mutually exclusive"
+ expect(last_response.body).to eq 'beer, wine are mutually exclusive, scotch, aquavit are mutually exclusive, scotch2, aquavit2 are mutually exclusive'
end
end
+
+ context 'in a group' do
+ it 'works when only one from the set is present' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ mutually_exclusive :beer, :wine, :juice
+ end
+ end
+ subject.get '/mutually_exclusive_group' do
+ 'mutually_exclusive_group works!'
+ end
+
+ get '/mutually_exclusive_group', drink: { beer: 'true' }
+ expect(last_response.status).to eq(200)
+ end
+
+ it 'errors when more than one from the set is present' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ mutually_exclusive :beer, :wine, :juice
+ end
+ end
+ subject.get '/mutually_exclusive_group' do
+ 'mutually_exclusive_group works!'
+ end
+
+ get '/mutually_exclusive_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
+ expect(last_response.status).to eq(400)
+ end
+ end
end
context 'exactly one of' do
context 'params' do
before :each do
@@ -1000,11 +1111,11 @@
end
it 'errors when none are present' do
get '/exactly_one_of'
expect(last_response.status).to eq(400)
- expect(last_response.body).to eq "beer, wine, juice are missing, exactly one parameter must be provided"
+ expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided'
end
it 'succeeds when one is present' do
get '/exactly_one_of', beer: 'string'
expect(last_response.status).to eq(200)
@@ -1012,13 +1123,53 @@
end
it 'errors when two or more are present' do
get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
expect(last_response.status).to eq(400)
- expect(last_response.body).to eq "beer, wine are mutually exclusive"
+ expect(last_response.body).to eq 'beer, wine are mutually exclusive'
end
end
+
+ context 'nested params' do
+ before :each do
+ subject.params do
+ requires :nested, type: Hash do
+ optional :beer_nested
+ optional :wine_nested
+ optional :juice_nested
+ exactly_one_of :beer_nested, :wine_nested, :juice_nested
+ end
+ optional :nested2, type: Array do
+ optional :beer_nested2
+ optional :wine_nested2
+ optional :juice_nested2
+ exactly_one_of :beer_nested2, :wine_nested2, :juice_nested2
+ end
+ end
+ subject.get '/exactly_one_of_nested' do
+ 'exactly_one_of works!'
+ end
+ end
+
+ it 'errors when none are present' do
+ get '/exactly_one_of_nested'
+ expect(last_response.status).to eq(400)
+ expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, exactly one parameter must be provided'
+ end
+
+ it 'succeeds when one is present' do
+ get '/exactly_one_of_nested', nested: { beer_nested: 'string' }
+ expect(last_response.status).to eq(200)
+ expect(last_response.body).to eq 'exactly_one_of works!'
+ end
+
+ it 'errors when two or more are present' do
+ get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }]
+ expect(last_response.status).to eq(400)
+ expect(last_response.body).to eq 'beer_nested2, wine_nested2 are mutually exclusive'
+ end
+ end
end
context 'at least one of' do
context 'params' do
before :each do
@@ -1034,11 +1185,11 @@
end
it 'errors when none are present' do
get '/at_least_one_of'
expect(last_response.status).to eq(400)
- expect(last_response.body).to eq "beer, wine, juice are missing, at least one parameter must be provided"
+ expect(last_response.body).to eq 'beer, wine, juice are missing, at least one parameter must be provided'
end
it 'does not error when one is present' do
get '/at_least_one_of', beer: 'string'
expect(last_response.status).to eq(200)
@@ -1048,9 +1199,123 @@
it 'does not error when two are present' do
get '/at_least_one_of', beer: 'string', wine: 'string'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'at_least_one_of works!'
end
+ end
+
+ context 'nested params' do
+ before :each do
+ subject.params do
+ requires :nested, type: Hash do
+ optional :beer_nested
+ optional :wine_nested
+ optional :juice_nested
+ at_least_one_of :beer_nested, :wine_nested, :juice_nested
+ end
+ optional :nested2, type: Array do
+ optional :beer_nested2
+ optional :wine_nested2
+ optional :juice_nested2
+ at_least_one_of :beer_nested2, :wine_nested2, :juice_nested2
+ end
+ end
+ subject.get '/at_least_one_of_nested' do
+ 'at_least_one_of works!'
+ end
+ end
+
+ it 'errors when none are present' do
+ get '/at_least_one_of_nested'
+ expect(last_response.status).to eq(400)
+ expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, at least one parameter must be provided'
+ end
+
+ it 'does not error when one is present' do
+ get '/at_least_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string' }]
+ expect(last_response.status).to eq(200)
+ expect(last_response.body).to eq 'at_least_one_of works!'
+ end
+
+ it 'does not error when two are present' do
+ get '/at_least_one_of_nested', nested: { beer_nested: 'string', wine_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'string' }]
+ expect(last_response.status).to eq(200)
+ expect(last_response.body).to eq 'at_least_one_of works!'
+ end
+ end
+ end
+
+ context 'in a group' do
+ it 'works when only one from the set is present' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ exactly_one_of :beer, :wine, :juice
+ end
+ end
+ subject.get '/exactly_one_of_group' do
+ 'exactly_one_of_group works!'
+ end
+
+ get '/exactly_one_of_group', drink: { beer: 'true' }
+ expect(last_response.status).to eq(200)
+ end
+
+ it 'errors when no parameter from the set is present' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ exactly_one_of :beer, :wine, :juice
+ end
+ end
+ subject.get '/exactly_one_of_group' do
+ 'exactly_one_of_group works!'
+ end
+
+ get '/exactly_one_of_group', drink: {}
+ expect(last_response.status).to eq(400)
+ end
+
+ it 'errors when more than one from the set is present' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ exactly_one_of :beer, :wine, :juice
+ end
+ end
+ subject.get '/exactly_one_of_group' do
+ 'exactly_one_of_group works!'
+ end
+
+ get '/exactly_one_of_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
+ expect(last_response.status).to eq(400)
+ end
+
+ it 'does not falsely think the param is there if it is provided outside the block' do
+ subject.params do
+ group :drink, type: Hash do
+ optional :wine
+ optional :beer
+ optional :juice
+
+ exactly_one_of :beer, :wine, :juice
+ end
+ end
+ subject.get '/exactly_one_of_group' do
+ 'exactly_one_of_group works!'
+ end
+
+ get '/exactly_one_of_group', drink: { foo: 'bar' }, beer: 'true'
+ expect(last_response.status).to eq(400)
end
end
end
end