Validating ExceptValues Parameter Validation in ruby-grape/grape
This test suite validates the ExceptValuesValidator functionality in Grape’s parameter validation system, focusing on handling excluded values, type coercion, and custom error messages. The tests ensure proper validation behavior for both required and optional parameters with various configuration options.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
ruby-grape/grape
spec/grape/validations/validators/except_values_validator_spec.rb
# frozen_string_literal: true
describe Grape::Validations::Validators::ExceptValuesValidator do
describe 'IncompatibleOptionValues' do
subject { api }
context 'when a default value is set' do
let(:api) do
ev = except_values
dv = default_value
Class.new(Grape::API) do
params do
optional :type, except_values: ev, default: dv
end
end
end
context 'when default value is in exclude' do
let(:except_values) { 1..10 }
let(:default_value) { except_values.to_a.sample }
it 'raises IncompatibleOptionValues' do
expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
context 'when default array has excluded values' do
let(:except_values) { 1..10 }
let(:default_value) { [8, 9, 10] }
it 'raises IncompatibleOptionValues' do
expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
end
context 'when type is incompatible' do
let(:api) do
Class.new(Grape::API) do
params do
optional :type, except_values: 1..10, type: Symbol
end
end
end
it 'raises IncompatibleOptionValues' do
expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
end
{
req_except: {
requires: { except_values: %w[invalid-type1 invalid-type2 invalid-type3] },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }
]
},
req_except_hash: {
requires: { except_values: { value: %w[invalid-type1 invalid-type2 invalid-type3] } },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }
]
},
req_except_custom_message: {
requires: { except_values: { value: %w[invalid-type1 invalid-type2 invalid-type3], message: 'is not allowed' } },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type is not allowed' }.to_json },
{ value: 'invalid-type3', rc: 400, body: { error: 'type is not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }
]
},
req_except_no_value: {
requires: { except_values: { message: 'is not allowed' } },
tests: [
{ value: 'invalid-type1', rc: 200, body: { type: 'invalid-type1' }.to_json }
]
},
req_except_empty: {
requires: { except_values: [] },
tests: [
{ value: 'invalid-type1', rc: 200, body: { type: 'invalid-type1' }.to_json }
]
},
req_except_lambda: {
requires: { except_values: -> { %w[invalid-type1 invalid-type2 invalid-type3 invalid-type4] } },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'invalid-type4', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }
]
},
req_except_lambda_custom_message: {
requires: { except_values: { value: -> { %w[invalid-type1 invalid-type2 invalid-type3 invalid-type4] }, message: 'is not allowed' } },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type is not allowed' }.to_json },
{ value: 'invalid-type4', rc: 400, body: { error: 'type is not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }
]
},
opt_except_default: {
optional: { except_values: %w[invalid-type1 invalid-type2 invalid-type3], default: 'valid-type2' },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json },
{ rc: 200, body: { type: 'valid-type2' }.to_json }
]
},
opt_except_lambda_default: {
optional: { except_values: -> { %w[invalid-type1 invalid-type2 invalid-type3] }, default: 'valid-type2' },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json },
{ rc: 200, body: { type: 'valid-type2' }.to_json }
]
},
req_except_type_coerce: {
requires: { type: Integer, except_values: [10, 11] },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },
{ value: 11, rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: '11', rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: '3', rc: 200, body: { type: 3 }.to_json },
{ value: 3, rc: 200, body: { type: 3 }.to_json }
]
},
opt_except_type_coerce_default: {
optional: { type: Integer, except_values: [10, 11], default: 12 },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },
{ value: 10, rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: '3', rc: 200, body: { type: 3 }.to_json },
{ value: 3, rc: 200, body: { type: 3 }.to_json },
{ rc: 200, body: { type: 12 }.to_json }
]
},
opt_except_array_type_coerce_default: {
optional: { type: Array[Integer], except_values: [10, 11], default: 12 },
tests: [
{ value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },
{ value: 10, rc: 400, body: { error: 'type is invalid' }.to_json },
{ value: [10], rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: ['3'], rc: 200, body: { type: [3] }.to_json },
{ value: [3], rc: 200, body: { type: [3] }.to_json },
{ rc: 200, body: { type: 12 }.to_json }
]
},
req_except_range: {
optional: { type: Integer, except_values: 10..12 },
tests: [
{ value: 11, rc: 400, body: { error: 'type has a value not allowed' }.to_json },
{ value: 13, rc: 200, body: { type: 13 }.to_json }
]
}
}.each do |path, param_def|
param_def[:tests].each do |t|
describe "when #{path}" do
let(:app) do
Class.new(Grape::API) do
default_format :json
params do
requires :type, param_def[:requires] if param_def.key? :requires
optional :type, param_def[:optional] if param_def.key? :optional
end
get path do
{ type: params[:type] }
end
end
end
let(:body) do
{}.tap do |body|
body[:type] = t[:value] if t.key? :value
end
end
before do
get path.to_s, **body
end
it "returns body #{t[:body]} with status #{t[:rc]}" do
expect(last_response.status).to eq t[:rc]
expect(last_response.body).to eq t[:body]
end
end
end
end
end