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

- old
+ new

@@ -17,16 +17,16 @@ subject.get '/optional' do 'optional works!' end get '/optional', a_number: 'string' - last_response.status.should == 400 - last_response.body.should == 'a_number is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('a_number is invalid') get '/optional', a_number: 45 - last_response.status.should == 200 - last_response.body.should == 'optional works!' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('optional works!') end it "doesn't validate when param not present" do subject.params do optional :a_number, regexp: /^[0-9]+$/ @@ -34,19 +34,19 @@ subject.get '/optional' do 'optional works!' end get '/optional' - last_response.status.should == 200 - last_response.body.should == 'optional works!' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('optional works!') end it 'adds to declared parameters' do subject.params do optional :some_param end - subject.settings[:declared_params].should == [:some_param] + expect(subject.settings[:declared_params]).to eq([:some_param]) end end context 'required' do before do @@ -58,33 +58,33 @@ end end it 'errors when param not present' do get '/required' - last_response.status.should == 400 - last_response.body.should == 'key is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('key is missing') end it "doesn't throw a missing param when param is present" do get '/required', key: 'cool' - last_response.status.should == 200 - last_response.body.should == 'required works' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('required works') end it 'adds to declared parameters' do subject.params do requires :some_param end - subject.settings[:declared_params].should == [:some_param] + expect(subject.settings[:declared_params]).to eq([:some_param]) end end context 'requires :all using Grape::Entity documentation' do def define_requires_all documentation = { - required_field: { type: String }, - optional_field: { type: String } + required_field: { type: String }, + optional_field: { type: String } } subject.params do requires :all, except: :optional_field, using: documentation end end @@ -95,31 +95,31 @@ end end it 'adds entity documentation to declared params' do define_requires_all - subject.settings[:declared_params].should == [:required_field, :optional_field] + expect(subject.settings[:declared_params]).to eq([: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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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 } + required_field: { type: String }, + optional_field: { type: String } } subject.params do requires :none, except: :required_field, using: documentation end end @@ -130,26 +130,61 @@ end end it 'adds entity documentation to declared params' do define_requires_none - subject.settings[:declared_params].should == [:required_field, :optional_field] + expect(subject.settings[:declared_params]).to eq([: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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('required works') end end + context 'requires :all or :none but except a non-existent field using Grape::Entity documentation' do + context 'requires :all' do + def define_requires_all + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } + subject.params do + requires :all, except: :non_existent_field, using: documentation + 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]) + end + end + + context 'requires :none' do + def define_requires_none + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } + subject.params do + requires :none, except: :non_existent_field, using: documentation + end + end + + it 'adds only the entity documentation to declared params, nothing more' do + expect { define_requires_none }.to raise_error(ArgumentError) + end + end + end + context 'required with an Array block' do before do subject.params do requires :items, type: Array do requires :key @@ -160,28 +195,28 @@ end end it 'errors when param not present' do get '/required' - last_response.status.should == 400 - last_response.body.should == 'items is missing' + 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" - last_response.status.should == 400 - last_response.body.should == 'items is invalid, items[key] is missing' + 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' } - last_response.status.should == 400 - last_response.body.should == 'items is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + 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 @@ -196,11 +231,11 @@ subject.params do requires :items do requires :key end end - subject.settings[:declared_params].should == [items: [:key]] + expect(subject.settings[:declared_params]).to eq([items: [:key]]) end end context 'required with a Hash block' do before do @@ -214,28 +249,28 @@ end end it 'errors when param not present' do get '/required' - last_response.status.should == 400 - last_response.body.should == 'items is missing, items[key] is missing' + 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" - last_response.status.should == 400 - last_response.body.should == 'items is invalid, items[key] is missing' + 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' }] - last_response.status.should == 400 - last_response.body.should == 'items is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('items is invalid') end it "doesn't throw a missing param when param is present" do get '/required', items: { key: 'hello' } - last_response.status.should == 200 - last_response.body.should == 'required works' + 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 @@ -250,11 +285,11 @@ subject.params do requires :items do requires :key end end - subject.settings[:declared_params].should == [items: [:key]] + expect(subject.settings[:declared_params]).to eq([items: [:key]]) end end context 'group' do before do @@ -268,27 +303,27 @@ end end it 'errors when param not present' do get '/required' - last_response.status.should == 400 - last_response.body.should == 'items is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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 - last_response.body.should == 'required works' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('required works') end it 'adds to declared parameters' do subject.params do group :items do requires :key end end - subject.settings[:declared_params].should == [items: [:key]] + expect(subject.settings[:declared_params]).to eq([items: [:key]]) end end context 'validation within arrays' do before do @@ -308,49 +343,49 @@ 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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('children is missing') get '/within_array', children: [name: 'Jay'] - last_response.status.should == 400 - last_response.body.should == 'children[parents] is missing' + 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 # 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' + 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' } - last_response.status.should == 400 - last_response.body.should == 'children is invalid, children[parents] is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('children[parents] is invalid') end end context 'with block param' do before do @@ -392,57 +427,57 @@ 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' + 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' } - last_response.status.should == 400 - last_response.body.should == 'planets is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('planets is invalid') get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }] - last_response.status.should == 200 + expect(last_response.status).to eq(200) put_with_json '/req', planets: [] - last_response.status.should == 200 + expect(last_response.status).to eq(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' + 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' } - last_response.status.should == 400 - last_response.body.should == 'moons is invalid' + 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' }] - last_response.status.should == 200 + expect(last_response.status).to eq(200) put_with_json '/opt', name: "Venus" - last_response.status.should == 200 + expect(last_response.status).to eq(200) put_with_json '/opt', name: "Mercury", moons: [] - last_response.status.should == 200 + expect(last_response.status).to eq(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' + 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' } - last_response.status.should == 400 - last_response.body.should == 'stars is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('stars is invalid') get '/grp', stars: [{ name: 'Sun' }] - last_response.status.should == 200 + expect(last_response.status).to eq(200) put_with_json '/grp', stars: [] - last_response.status.should == 200 + expect(last_response.status).to eq(200) end end context 'validation within arrays with JSON' do before do @@ -462,29 +497,29 @@ 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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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 + expect(last_response.status).to eq(200) put_with_json '/within_array', children: [name: 'Jay'] - last_response.status.should == 400 - last_response.body.should == 'children[parents] is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('children[parents] is missing') end end context 'optional with an Array block' do before do @@ -498,43 +533,43 @@ end end it "doesn't throw a missing param when the group isn't present" do get '/optional_group' - last_response.status.should == 200 - last_response.body.should == 'optional group works' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' }] - last_response.status.should == 200 - last_response.body.should == 'optional group works' + 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 get '/optional_group', items: [{ not_key: 'foo' }] - last_response.status.should == 400 - last_response.body.should == 'items[key] is missing' + 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" - last_response.status.should == 400 - last_response.body.should == 'items is invalid, items[key] is missing' + 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' } - last_response.status.should == 400 - last_response.body.should == 'items is invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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]] + expect(subject.settings[:declared_params]).to eq([items: [:key]]) end end context 'nested optional Array blocks' do before do @@ -548,57 +583,57 @@ subject.get('/nested_optional_group') { 'nested optional group works' } end it 'does no internal validations if the outer group is blank' do get '/nested_optional_group' - last_response.status.should == 200 - last_response.body.should == 'nested optional group works' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('nested optional group works') end it 'does internal validations if the outer group is present' do get '/nested_optional_group', items: [{ key: 'foo' }] - last_response.status.should == 400 - last_response.body.should == 'items[required_subitems] is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' }] }] - last_response.status.should == 400 - last_response.body.should == 'items[optional_subitems][value] is missing' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('items[optional_subitems][value] is missing') 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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('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' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('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 } requires(:required_subitems) { requires :value } end end - subject.settings[:declared_params].should == [items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]] + expect(subject.settings[:declared_params]).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]) end end context 'multiple validation errors' do before do @@ -611,13 +646,13 @@ end end it 'throws the validation errors' do get '/two_required' - last_response.status.should == 400 - last_response.body.should =~ /yolo is missing/ - last_response.body.should =~ /swag is missing/ + expect(last_response.status).to eq(400) + expect(last_response.body).to match(/yolo is missing/) + expect(last_response.body).to match(/swag is missing/) end end context 'custom validation' do module CustomValidations @@ -640,32 +675,32 @@ end end it 'validates when param is present' do get '/optional_custom', custom: 'im custom' - last_response.status.should == 200 - last_response.body.should == 'optional with custom works!' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('optional with custom works!') get '/optional_custom', custom: 'im wrong' - last_response.status.should == 400 - last_response.body.should == 'custom is not custom!' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('custom is not custom!') end it "skips validation when parameter isn't present" do get '/optional_custom' - last_response.status.should == 200 - last_response.body.should == 'optional with custom works!' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('optional with custom works!') end it 'validates with custom validator when param present and incorrect type' do subject.params do optional :custom, type: String, customvalidator: true end get '/optional_custom', custom: 123 - last_response.status.should == 400 - last_response.body.should == 'custom is not custom!' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('custom is not custom!') end end context 'when using requires with a custom validator' do before do @@ -677,22 +712,22 @@ end end it 'validates when param is present' do get '/required_custom', custom: 'im wrong, validate me' - last_response.status.should == 400 - last_response.body.should == 'custom is not custom!' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('custom is not custom!') get '/required_custom', custom: 'im custom' - last_response.status.should == 200 - last_response.body.should == 'required with custom works!' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('required with custom works!') end it 'validates when param is not present' do get '/required_custom' - last_response.status.should == 400 - last_response.body.should == 'custom is missing, custom is not custom!' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('custom is missing, custom is not custom!') end context 'nested namespaces' do before do subject.params do @@ -735,37 +770,37 @@ end end specify 'the parent namespace uses the validator' do get '/nested/one', custom: 'im wrong, validate me' - last_response.status.should == 400 - last_response.body.should == 'custom is not custom!' + 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 get '/nested/nested/two', custom: 'im wrong, validate me' - last_response.status.should == 400 - last_response.body.should == 'custom is not custom!' + 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 get '/peer/one', custom: 'im not validated' - last_response.status.should == 200 - last_response.body.should == 'no validation required' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('no validation required') end specify 'namespaces nested in peers should also not have the validator' do get '/peer/nested/two', custom: 'im not validated' - last_response.status.should == 200 - last_response.body.should == 'no validation required' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('no validation required') end specify 'when nested, specifying a route should clear out the validations for deeper nested params' do get '/unrelated/one' - last_response.status.should == 400 + expect(last_response.status).to eq(400) get '/unrelated/double/two' - last_response.status.should == 200 + expect(last_response.status).to eq(200) end end end end # end custom validation @@ -809,21 +844,62 @@ it 'by #use' do subject.params do use :pagination end - subject.settings[:declared_params].should eq [:page, :per_page] + expect(subject.settings[:declared_params]).to 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] + expect(subject.settings[:declared_params]).to eq [:page, :per_page, :start_date, :end_date] end end + + context 'with block' do + before do + subject.helpers do + params :order do |options| + optional :order, type: Symbol, values: [:asc, :desc], default: options[:default_order] + optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by] + end + end + subject.format :json + subject.params do + use :order, default_order: :asc, order_by: [:name, :created_at], default_order_by: :created_at + end + subject.get '/order' do + { + order: params[:order], + order_by: params[:order_by] + } + end + end + it 'returns defaults' do + get '/order' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({ order: :asc, order_by: :created_at }.to_json) + end + it 'overrides default value for order' do + get '/order?order=desc' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({ order: :desc, order_by: :created_at }.to_json) + end + it 'overrides default value for order_by' do + get '/order?order_by=name' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({ order: :asc, order_by: :name }.to_json) + end + it 'fails with invalid value' do + get '/order?order=invalid' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('{"error":"order does not have a valid value"}') + end + end end context 'documentation' do it 'can be included with a hash' do documentation = { example: 'Joe' } @@ -832,10 +908,87 @@ requires 'first_name', documentation: documentation end subject.get '/' do end - subject.routes.first.route_params['first_name'][:documentation].should eq(documentation) + expect(subject.routes.first.route_params['first_name'][:documentation]).to eq(documentation) + end + end + + context 'mutually exclusive' do + context 'optional params' do + it 'errors when two or more are present' do + subject.params do + optional :beer + optional :wine + optional :juice + mutually_exclusive :beer, :wine, :juice + end + subject.get '/mutually_exclusive' do + '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") + 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 + end + subject.get '/mutually_exclusive' do + 'mutually_exclusive works!' + end + + get '/mutually_exclusive', beer: 'true', wine: 'true', scotch: 'true', aquavit: 'true' + expect(last_response.status).to eq(400) + expect(last_response.body).to match(/\[:beer, :wine\] are mutually exclusive/) + expect(last_response.body).to match(/\[:scotch, :aquavit\] are mutually exclusive/) + end + end + end + + context 'exactly one of' do + context 'params' do + it 'errors when two or more are present' do + subject.params do + optional :beer + optional :wine + optional :juice + exactly_one_of :beer, :wine, :juice + end + subject.get '/exactly_one_of' do + 'exactly_one_of works!' + end + + 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") + end + + it 'errors when none is selected' do + subject.params do + optional :beer + optional :wine + optional :juice + exactly_one_of :beer, :wine, :juice + end + subject.get '/exactly_one_of' do + 'exactly_one_of works!' + end + + get '/exactly_one_of' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq("[:beer, :wine, :juice] - exactly one parameter must be provided") + end end end end end