spec/grape/validations_spec.rb in grape-0.6.1 vs spec/grape/validations_spec.rb in grape-0.7.0

- old
+ new

@@ -76,35 +76,169 @@ end subject.settings[:declared_params].should == [:some_param] end end - context 'required with a block' do + context 'requires :all using Grape::Entity documentation' do + def define_requires_all + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } + subject.params do + requires :all, except: :optional_field, using: documentation + end + end before do + define_requires_all + subject.get '/required' do + 'required works' + end + end + + it 'adds entity documentation to declared params' do + define_requires_all + subject.settings[:declared_params].should == [:required_field, :optional_field] + end + + it 'errors when required_field is not present' do + get '/required' + last_response.status.should == 400 + last_response.body.should == 'required_field is missing' + end + + it 'works when required_field is present' do + get '/required', required_field: 'woof' + last_response.status.should == 200 + last_response.body.should == 'required works' + end + end + + context 'requires :none using Grape::Entity documentation' do + def define_requires_none + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } subject.params do + requires :none, except: :required_field, using: documentation + end + end + before do + define_requires_none + subject.get '/required' do + 'required works' + end + end + + it 'adds entity documentation to declared params' do + define_requires_none + subject.settings[:declared_params].should == [:required_field, :optional_field] + end + + it 'errors when required_field is not present' do + get '/required' + last_response.status.should == 400 + last_response.body.should == 'required_field is missing' + end + + it 'works when required_field is present' do + get '/required', required_field: 'woof' + last_response.status.should == 200 + last_response.body.should == 'required works' + end + end + + context 'required with an Array block' do + before do + subject.params do + requires :items, type: Array do + requires :key + end + end + subject.get '/required' do + 'required works' + end + end + + it 'errors when param not present' do + get '/required' + last_response.status.should == 400 + last_response.body.should == 'items is missing' + end + + it "errors when param is not an Array" do + get '/required', items: "hello" + last_response.status.should == 400 + last_response.body.should == 'items is invalid, items[key] is missing' + + get '/required', items: { key: 'foo' } + last_response.status.should == 400 + last_response.body.should == 'items is invalid' + end + + it "doesn't throw a missing param when param is present" do + get '/required', items: [{ key: 'hello' }, { key: 'world' }] + last_response.status.should == 200 + last_response.body.should == '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 + subject.settings[:declared_params].should == [items: [:key]] + end + end + + context 'required with a Hash block' do + before do + subject.params do + requires :items, type: Hash do + requires :key + end + end subject.get '/required' do 'required works' end end it 'errors when param not present' do get '/required' last_response.status.should == 400 - last_response.body.should == 'items[key] is missing' + last_response.body.should == 'items is missing, items[key] is missing' end + it "errors when param is not a Hash" do + get '/required', items: "hello" + last_response.status.should == 400 + last_response.body.should == 'items is invalid, items[key] is missing' + + get '/required', items: [{ key: 'foo' }] + last_response.status.should == 400 + last_response.body.should == 'items is invalid' + end + it "doesn't throw a missing param when param is present" do - get '/required', items: [key: 'hello', key: 'world'] + get '/required', items: { key: 'hello' } last_response.status.should == 200 last_response.body.should == 'required works' end - it "doesn't allow more than one parameter" do + 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 @@ -135,11 +269,11 @@ end it 'errors when param not present' do get '/required' last_response.status.should == 400 - last_response.body.should == 'items[key] is missing' + last_response.body.should == 'items is missing' end it "doesn't throw a missing param when param is present" do get '/required', items: [key: 'hello', key: 'world'] last_response.status.should == 200 @@ -154,14 +288,210 @@ end subject.settings[:declared_params].should == [items: [:key]] end end - context 'optional with a block' do + context 'validation within arrays' do before do subject.params do - optional :items do + group :children do + requires :name + group :parents do + requires :name + end + end + end + subject.get '/within_array' do + 'within array works' + end + end + + it 'can handle new scopes within child elements' do + get '/within_array', children: [ + { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] }, + { name: 'Joe', parents: [{ name: 'Josie' }] } + ] + last_response.status.should == 200 + last_response.body.should == 'within array works' + end + + it 'errors when a parameter is not present' do + get '/within_array', children: [ + { name: 'Jim', parents: [{}] }, + { name: 'Job', parents: [{ name: 'Joy' }] } + ] + # NOTE: with body parameters in json or XML or similar this + # should actually fail with: children[parents][name] is missing. + last_response.status.should == 400 + last_response.body.should == 'children[parents] is missing' + end + + it 'safely handles empty arrays and blank parameters' do + # NOTE: with body parameters in json or XML or similar this + # should actually return 200, since an empty array is valid. + get '/within_array', children: [] + last_response.status.should == 400 + last_response.body.should == 'children is missing' + get '/within_array', children: [name: 'Jay'] + last_response.status.should == 400 + last_response.body.should == 'children[parents] is missing' + end + + 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" + last_response.status.should == 400 + last_response.body.should == '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' } + last_response.status.should == 400 + last_response.body.should == 'children is invalid, children[parents] is missing' + + get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }] + last_response.status.should == 400 + last_response.body.should == 'children[parents] is invalid' + end + end + + context 'with block param' do + before do + subject.params do + requires :planets do + requires :name + end + end + subject.get '/req' do + 'within array works' + end + subject.put '/req' do + '' + end + + subject.params do + group :stars do + requires :name + end + end + subject.get '/grp' do + 'within array works' + end + subject.put '/grp' do + '' + end + + subject.params do + requires :name + optional :moons do + requires :name + end + end + subject.get '/opt' do + 'within array works' + end + subject.put '/opt' do + '' + end + end + + it 'requires defaults to Array type' do + get '/req', planets: "Jupiter, Saturn" + last_response.status.should == 400 + last_response.body.should == 'planets is invalid, planets[name] is missing' + + get '/req', planets: { name: 'Jupiter' } + last_response.status.should == 400 + last_response.body.should == 'planets is invalid' + + get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }] + last_response.status.should == 200 + + put_with_json '/req', planets: [] + last_response.status.should == 200 + end + + it 'optional defaults to Array type' do + get '/opt', name: "Jupiter", moons: "Europa, Ganymede" + last_response.status.should == 400 + last_response.body.should == 'moons is invalid, moons[name] is missing' + + get '/opt', name: "Jupiter", moons: { name: 'Ganymede' } + last_response.status.should == 400 + last_response.body.should == 'moons is invalid' + + get '/opt', name: "Jupiter", moons: [{ name: 'Io' }, { name: 'Callisto' }] + last_response.status.should == 200 + + put_with_json '/opt', name: "Venus" + last_response.status.should == 200 + + put_with_json '/opt', name: "Mercury", moons: [] + last_response.status.should == 200 + end + + it 'group defaults to Array type' do + get '/grp', stars: "Sun" + last_response.status.should == 400 + last_response.body.should == 'stars is invalid, stars[name] is missing' + + get '/grp', stars: { name: 'Sun' } + last_response.status.should == 400 + last_response.body.should == 'stars is invalid' + + get '/grp', stars: [{ name: 'Sun' }] + last_response.status.should == 200 + + put_with_json '/grp', stars: [] + last_response.status.should == 200 + end + end + + context 'validation within arrays with JSON' do + before do + subject.params do + group :children do + requires :name + group :parents do + requires :name + end + end + end + subject.put '/within_array' do + 'within array works' + end + end + + it 'can handle new scopes within child elements' do + put_with_json '/within_array', children: [ + { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] }, + { name: 'Joe', parents: [{ name: 'Josie' }] } + ] + last_response.status.should == 200 + last_response.body.should == 'within array works' + end + + it 'errors when a parameter is not present' do + put_with_json '/within_array', children: [ + { name: 'Jim', parents: [{}] }, + { name: 'Job', parents: [{ name: 'Joy' }] } + ] + last_response.status.should == 400 + last_response.body.should == 'children[parents][name] is missing' + end + + it 'safely handles empty arrays and blank parameters' do + put_with_json '/within_array', children: [] + last_response.status.should == 200 + put_with_json '/within_array', children: [name: 'Jay'] + last_response.status.should == 400 + last_response.body.should == 'children[parents] is missing' + end + end + + context 'optional with an Array block' do + before do + subject.params do + optional :items, type: Array do requires :key end end subject.get '/optional_group' do 'optional group works' @@ -173,38 +503,48 @@ last_response.status.should == 200 last_response.body.should == 'optional group works' end it "doesn't throw a missing param when both group and param are given" do - get '/optional_group', items: { key: 'foo' } + get '/optional_group', items: [{ key: 'foo' }] last_response.status.should == 200 last_response.body.should == 'optional group works' end it "errors when group is present, but required param is not" do - get '/optional_group', items: { not_key: 'foo' } + get '/optional_group', items: [{ not_key: 'foo' }] last_response.status.should == 400 last_response.body.should == 'items[key] is missing' end + it "errors when param is present but isn't an Array" do + get '/optional_group', items: "hello" + last_response.status.should == 400 + last_response.body.should == 'items is invalid, items[key] is missing' + + get '/optional_group', items: { key: 'foo' } + last_response.status.should == 400 + last_response.body.should == 'items is invalid' + end + it 'adds to declared parameters' do subject.params do optional :items do requires :key end end subject.settings[:declared_params].should == [items: [:key]] end end - context 'nested optional blocks' do + context 'nested optional Array blocks' do before do subject.params do - optional :items do + optional :items, type: Array do requires :key - optional(:optional_subitems) { requires :value } - requires(:required_subitems) { requires :value } + optional(:optional_subitems, type: Array) { requires :value } + requires(:required_subitems, type: Array) { requires :value } end end subject.get('/nested_optional_group') { 'nested optional group works' } end @@ -213,29 +553,43 @@ last_response.status.should == 200 last_response.body.should == 'nested optional group works' end it 'does internal validations if the outer group is present' do - get '/nested_optional_group', items: { key: 'foo' } + get '/nested_optional_group', items: [{ key: 'foo' }] last_response.status.should == 400 - last_response.body.should == 'items[required_subitems][value] is missing' + last_response.body.should == 'items[required_subitems] is missing' - get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' } } + get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }] last_response.status.should == 200 last_response.body.should == 'nested optional group works' end it 'handles deep nesting' do - get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' }, optional_subitems: { not_value: 'baz' } } + get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }] last_response.status.should == 400 last_response.body.should == 'items[optional_subitems][value] is missing' - get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' }, optional_subitems: { value: 'baz' } } + get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }] last_response.status.should == 200 last_response.body.should == 'nested optional group works' end + it 'handles validation within arrays' do + get '/nested_optional_group', items: [{ key: 'foo' }] + last_response.status.should == 400 + last_response.body.should == 'items[required_subitems] is missing' + + get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }] + last_response.status.should == 200 + last_response.body.should == 'nested optional group works' + + get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }] + last_response.status.should == 400 + last_response.body.should == 'items[optional_subitems][value] is missing' + end + it 'adds to declared parameters' do subject.params do optional :items do requires :key optional(:optional_subitems) { requires :value } @@ -285,15 +639,15 @@ 'optional with custom works!' end end it 'validates when param is present' do - get '/optional_custom', { custom: 'im custom' } + get '/optional_custom', custom: 'im custom' last_response.status.should == 200 last_response.body.should == 'optional with custom works!' - get '/optional_custom', { custom: 'im wrong' } + get '/optional_custom', custom: 'im wrong' last_response.status.should == 400 last_response.body.should == 'custom is not custom!' end it "skips validation when parameter isn't present" do @@ -412,7 +766,76 @@ last_response.status.should == 200 end end end end # end custom validation + + context 'named' do + context 'can be defined' do + it 'in helpers' do + subject.helpers do + params :pagination do + end + end + end + + it 'in helper module which kind of Grape::API::Helpers' do + module SharedParams + extend Grape::API::Helpers + params :pagination do + end + end + subject.helpers SharedParams + end + end + + context 'can be included in usual params' do + before do + module SharedParams + extend Grape::API::Helpers + params :period do + optional :start_date + optional :end_date + end + end + subject.helpers SharedParams + + subject.helpers do + params :pagination do + optional :page, type: Integer + optional :per_page, type: Integer + end + end + end + + it 'by #use' do + subject.params do + use :pagination + end + subject.settings[:declared_params].should eq [:page, :per_page] + end + + it 'by #use with multiple params' do + subject.params do + use :pagination, :period + end + subject.settings[:declared_params].should eq [:page, :per_page, :start_date, :end_date] + end + + end + end + + context 'documentation' do + it 'can be included with a hash' do + documentation = { example: 'Joe' } + + subject.params do + requires 'first_name', documentation: documentation + end + subject.get '/' do + end + + subject.routes.first.route_params['first_name'][:documentation].should eq(documentation) + end + end end end