dry-validation/spec/integration/contract/call_spec.rb

138 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, "#call" do
subject(:contract) do
Class.new(Dry::Validation::Contract) do
def self.name
"TestContract"
end
params do
required(:email).filled(:string)
required(:age).filled(:integer)
optional(:login).maybe(:string, :filled?)
optional(:password).maybe(:string, min_size?: 10)
optional(:password_confirmation).maybe(:string)
optional(:address).hash do
required(:country).value(:string)
required(:zip).value(:string)
optional(:geolocation).hash do
required(:lon).value(:float)
required(:lat).value(:float)
end
end
end
rule(:login) do
if key?
key.failure("too short") if value.length < 3
end
end
rule(address: {geolocation: [:lon, :lat]}) do
if key?
lon, lat = value
key("address.geolocation.lat").failure("invalid") if lat < 10
key("address.geolocation.lon").failure("invalid") if lon < 10
end
end
rule(:password) do
key.failure("is required") if values[:login] && !values[:password]
end
rule(:age) do
key.failure("must be greater or equal 18") if value < 18
end
rule(:age) do
key.failure("must be greater than 0") if value < 0
end
rule(address: :zip) do
address = values[:address]
if address && address[:country] == "Russia" && address[:zip] != /\A\d{6}\z/
key.failure("must have 6 digit")
end
end
rule("address.geolocation.lon") do
key.failure("invalid longitude") if key? && !(-180.0...180.0).cover?(value)
end
end.new
end
it "applies rule to input processed by the schema" do
result = contract.(email: "john@doe.org", age: 19)
expect(result).to be_success
expect(result.errors.to_h).to eql({})
end
it "applies rule to an optional field when value is present" do
result = contract.(email: "john@doe.org", age: 19, login: "ab")
expect(result).to be_failure
expect(result.errors.to_h).to eql(login: ["too short"], password: ["is required"])
end
it "applies rule to an optional nested field when value is present" do
result = contract.(
email: "john@doe.org",
age: 19,
address: {
geolocation: {lat: 11, lon: 1},
country: "Poland",
zip: "12345"
}
)
expect(result).to be_failure
expect(result.errors.to_h).to eql(address: {geolocation: {lon: ["invalid"]}})
end
it "returns rule errors" do
result = contract.(email: "john@doe.org", login: "jane", age: 19)
expect(result).to be_failure
expect(result.errors.to_h).to eql(password: ["is required"])
end
it "doesn't execute rules when basic checks failed" do
result = contract.(email: "john@doe.org", age: "not-an-integer")
expect(result).to be_failure
expect(result.errors.to_h).to eql(age: ["must be an integer"])
end
it "gathers errors from multiple rules for the same key" do
result = contract.(email: "john@doe.org", age: -1)
expect(result).to be_failure
expect(result.errors.to_h).to eql(age: ["must be greater or equal 18", "must be greater than 0"])
end
it "builds nested message keys for nested rules" do
result = contract.(email: "john@doe.org", age: 20, address: {country: "Russia", zip: "abc"})
expect(result).to be_failure
expect(result.errors.to_h).to eql(address: {zip: ["must have 6 digit"]})
end
it "builds deeply nested messages for deeply nested rules" do
result = contract.(
email: "john@doe.org",
age: 20,
address: {
country: "UK", zip: "irrelevant",
geolocation: {lon: "365", lat: "78"}
}
)
expect(result).to be_failure
expect(result.errors.to_h).to eql(address: {geolocation: {lon: ["invalid longitude"]}})
end
end