spec/integration/schema_spec.rb in dry-validation-0.6.0 vs spec/integration/schema_spec.rb in dry-validation-0.7.0

- old
+ new

@@ -1,310 +1,140 @@ -RSpec.describe Dry::Validation::Schema do - subject(:validation) { schema.new } - - describe 'defining key-based schema (hash-like)' do - let(:schema) do - Class.new(Dry::Validation::Schema) do - key(:email) { |email| email.filled? } - - key(:age) do |age| - age.none? | (age.int? & age.gt?(18)) - end - - key(:address) do |address| - address.hash? do - address.key(:city) do |city| - city.min_size?(3) - end - - address.key(:street) do |street| - street.filled? - end - - address.key(:country) do |country| - country.key(:name, &:filled?) - country.key(:code, &:filled?) - end - end - end - - key(:phone_numbers) do |phone_numbers| - phone_numbers.array? { phone_numbers.each(&:str?) } - end +RSpec.describe Dry::Validation::Schema, 'defining key-based schema' do + describe 'with a flat structure' do + subject(:schema) do + Dry::Validation.Schema do + key(:email).required + key(:age) { none? | (int? & gt?(18)) } end end - let(:input) do - { - email: 'jane@doe.org', - age: 19, - address: { city: 'NYC', street: 'Street 1/2', country: { code: 'US', name: 'USA' } }, - phone_numbers: [ - '123456', '234567' - ] - }.freeze + it 'passes when input is valid' do + expect(schema.(email: 'jane@doe', age: 19)).to be_success + expect(schema.(email: 'jane@doe', age: nil)).to be_success end - describe '#messages' do - it 'returns compiled error messages' do - expect(validation.(input.merge(email: '')).messages).to match_array([ - [:email, [['email must be filled'], '']] - ]) - end + it 'fails when input is not valid' do + expect(schema.(email: 'jane@doe', age: 17)).to_not be_success end - describe '#call' do - it 'passes when attributes are valid' do - expect(validation.(input)).to be_empty - end + it 'returns result which quacks like hash' do + input = { email: 'jane@doe', age: 19 } + result = schema.(input) - it 'validates presence of an email and min age value' do - expect(validation.(input.merge(email: '', age: 18))).to match_array([ - [:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]], - [:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]] - ]) - end + expect(result[:email]).to eql('jane@doe') + expect(Hash[result]).to eql(input) - it 'validates presence of the email key and type of age value' do - expect(validation.(name: 'Jane', age: '18', address: input[:address], phone_numbers: input[:phone_numbers])).to match_array([ - [:error, [:input, [:age, "18", [[:val, [:age, [:predicate, [:int?, []]]]]]]]], - [:error, [:input, [:email, nil, [[:key, [:email, [:predicate, [:key?, [:email]]]]]]]]] - ]) - end - - it 'validates presence of the address and phone_number keys' do - expect(validation.(email: 'jane@doe.org', age: 19)).to match_array([ - [:error, [:input, [:address, nil, [[:key, [:address, [:predicate, [:key?, [:address]]]]]]]]], - [:error, [:input, [:phone_numbers, nil, [[:key, [:phone_numbers, [:predicate, [:key?, [:phone_numbers]]]]]]]]] - ]) - end - - it 'validates presence of keys under address and min size of the city value' do - expect(validation.(input.merge(address: { city: 'NY' }))).to match_array([ - [:error, [ - :input, [ - :address, {city: "NY"}, - [ - [:input, [:city, "NY", [[:val, [:city, [:predicate, [:min_size?, [3]]]]]]]], - [:input, [:street, nil, [[:key, [:street, [:predicate, [:key?, [:street]]]]]]]], - [:input, [:country, nil, [[:key, [:country, [:predicate, [:key?, [:country]]]]]]]] - ] - ] - ]] - ]) - end - - it 'validates address type' do - expect(validation.(input.merge(address: 'totally not a hash'))).to match_array([ - [:error, [:input, [:address, "totally not a hash", [[:val, [:address, [:predicate, [:hash?, []]]]]]]]] - ]) - end - - it 'validates address code and name values' do - expect(validation.(input.merge(address: input[:address].merge(country: { code: 'US', name: '' })))).to match_array([ - [:error, [ - :input, [ - :address, {city: "NYC", street: "Street 1/2", country: {code: "US", name: ""}}, - [ - [ - :input, [ - :country, {code: "US", name: ""}, [ - [ - :input, [ - :name, "", [[:val, [:name, [:predicate, [:filled?, []]]]]] - ] - ] - ] - ] - ] - ] - ] - ]] - ]) - end - - it 'validates each phone number' do - expect(validation.(input.merge(phone_numbers: ['123', 312]))).to match_array([ - [:error, [ - :input, [ - :phone_numbers, ["123", 312], - [ - [ - :input, [ - :phone_numbers, 312, [ - [:val, [:phone_numbers, [:predicate, [:str?, []]]]] - ] - ] - ] - ] - ] - ]] - ]) - end + expect(result.to_a).to eql([[:email, 'jane@doe'], [:age, 19]]) end end - describe 'defining attr-based schema (model-like)' do - let(:schema) do - Class.new(Dry::Validation::Schema) do - attr(:email) { |email| email.filled? } + describe 'with nested structures' do + subject(:schema) do + Dry::Validation.Schema do + key(:email).required - attr(:age) do |age| - age.none? | (age.int? & age.gt?(18)) - end + key(:age).maybe(:int?, gt?: 18) - attr(:address) do |address| - address.attr(:city) do |city| - city.min_size?(3) - end + key(:address).schema do + key(:city).required(min_size?: 3) - address.attr(:street) do |street| - street.filled? - end + key(:street).required - address.attr(:country) do |country| - country.attr(:name, &:filled?) - country.attr(:code, &:filled?) + key(:country).schema do + key(:name).required + key(:code).required end end - attr(:phone_numbers) do |phone_numbers| - phone_numbers.array? { phone_numbers.each(&:str?) } - end + key(:phone_numbers).each(:str?) end end - let(:input_data) do + let(:input) do { email: 'jane@doe.org', age: 19, address: { city: 'NYC', street: 'Street 1/2', country: { code: 'US', name: 'USA' } }, phone_numbers: [ '123456', '234567' ] }.freeze end - def input(data = input_data) - struct_from_hash(data) - end - describe '#messages' do it 'returns compiled error messages' do - expect(validation.(input(input_data.merge(email: ''))).messages).to match_array([ - [:email, [["email must be filled"], '']] - ]) + expect(schema.(input.merge(email: '')).messages).to eql( + email: ['must be filled'] + ) end end describe '#call' do it 'passes when attributes are valid' do - expect(validation.(input)).to be_empty + expect(schema.(input)).to be_success end it 'validates presence of an email and min age value' do - expect(validation.(input(input_data.merge(email: '', age: 18)))).to match_array([ - [:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]], - [:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]] - ]) + expect(schema.(input.merge(email: '', age: 18)).messages).to eql( + email: ['must be filled'], age: ['must be greater than 18'] + ) end - it 'validates presence of the email attr and type of age value' do - input_object = input(input_data.reject { |k, v| k == :email }.merge(age: '18')) + it 'validates presence of the email key and type of age value' do + attrs = { + name: 'Jane', + age: '18', + address: input[:address], phone_numbers: input[:phone_numbers] + } - expect(validation.(input_object)).to match_array([ - [:error, [:input, [:age, "18", [[:val, [:age, [:predicate, [:int?, []]]]]]]]], - [:error, [:input, [:email, input_object, [[:attr, [:email, [:predicate, [:attr?, [:email]]]]]]]]] - ]) + expect(schema.(attrs).messages).to eql( + email: ['is missing'], + age: ['must be an integer', 'must be greater than 18'] + ) end it 'validates presence of the address and phone_number keys' do - input_object = input(email: 'jane@doe.org', age: 19) + attrs = { email: 'jane@doe.org', age: 19 } - expect(validation.(input_object)).to match_array([ - [:error, [ - :input, [ - :address, input_object, - [ - [:attr, [:address, [:predicate, [:attr?, [:address]]]]] - ] - ] - ]], - [:error, [ - :input, [ - :phone_numbers, input_object, - [ - [:attr, [:phone_numbers, [:predicate, [:attr?, [:phone_numbers]]]]] - ] - ] - ]] - ]) + expect(schema.(attrs).messages).to eql( + address: ['is missing'], phone_numbers: ['is missing'] + ) end it 'validates presence of keys under address and min size of the city value' do - address = { city: 'NY' } - input_object = input(input_data.merge(address: address)) - address_object = input_object.address.class.from_hash(address) + attrs = input.merge(address: { city: 'NY' }) - expect(validation.(input_object)).to match_array([ - [:error, [ - :input, [ - :address, address_object, - [ - [:input, [:city, "NY", [[:val, [:city, [:predicate, [:min_size?, [3]]]]]]]], - [:input, [:street, address_object, [[:attr, [:street, [:predicate, [:attr?, [:street]]]]]]]], - [:input, [:country, address_object, [[:attr, [:country, [:predicate, [:attr?, [:country]]]]]]]] - ] - ] - ]] - ]) + expect(schema.(attrs).messages).to eql( + address: { + street: ['is missing'], + country: ['is missing'], + city: ['size cannot be less than 3'] + } + ) end + it 'validates address type' do + expect(schema.(input.merge(address: 'totally not a hash')).messages).to eql( + address: ['must be a hash'] + ) + end + it 'validates address code and name values' do - input_object = input(input_data.merge(address: input_data[:address].merge(country: { code: 'US', name: '' }))) + attrs = input.merge( + address: input[:address].merge(country: { code: 'US', name: '' }) + ) - country_object = input_object.address.country.class.from_hash(code: "US", name: "") - - expect(validation.(input_object)).to match_array([ - [:error, [ - :input, [ - :address, input_object.address.class.from_hash(city: "NYC", street: "Street 1/2", country: country_object), - [ - [ - :input, [ - :country, country_object, [ - [ - :input, [ - :name, "", [[:val, [:name, [:predicate, [:filled?, []]]]]] - ] - ] - ] - ] - ] - ] - ] - ]] - ]) + expect(schema.(attrs).messages).to eql( + address: { country: { name: ['must be filled'] } } + ) end it 'validates each phone number' do - input_object = input(input_data.merge(phone_numbers: ['123', 312])) + attrs = input.merge(phone_numbers: ['123', 312]) - expect(validation.(input_object)).to match_array([ - [:error, [ - :input, [ - :phone_numbers, ["123", 312],[ - [ - :input, [ - :phone_numbers, 312, [ - [:val, [:phone_numbers, [:predicate, [:str?, []]]]] - ] - ] - ] - ] - ] - ]] - ]) + expect(schema.(attrs).messages).to eql( + phone_numbers: { 1 => ['must be a string'] } + ) end end end end