require 'test_helper' module Schemacop module V3 class ArrayNodeTest < V3Test EXP_INVALID_TYPE = 'Invalid type, expected "array".'.freeze def test_basic schema :array assert_json(type: :array) assert_validation [] assert_validation [nil, nil] assert_validation [2234, 'foo', :bar] end def test_in_object schema { ary! :items } assert_json( type: :object, properties: { items: { type: :array } }, required: %i[items], additionalProperties: false ) assert_validation items: [] assert_validation items: [nil] assert_validation items: [2234, 'foo', :bar] end def test_object_contents schema :array do hsh do str! :name end end assert_json( type: :array, items: [ { type: :object, properties: { name: { type: :string } }, required: %i[name], additionalProperties: false } ], additionalItems: false ) assert_validation [{ name: 'Foo' }] assert_validation [{ name: 'Foo' }, { name: 'Bar' }] do error '/', 'Array has 2 items but must have exactly 1.' end assert_validation [123] do error '/[0]', 'Invalid type, expected "object".' end end def test_type schema :array assert_json(type: :array) assert_validation 42 do error '/', EXP_INVALID_TYPE end schema { ary! :foo } assert_json( type: :object, properties: { foo: { type: :array } }, required: %i[foo], additionalProperties: false ) assert_validation foo: 42 do error '/foo', EXP_INVALID_TYPE end assert_validation foo: {} do error '/foo', EXP_INVALID_TYPE end end def test_min_items schema :array, min_items: 0 assert_json(type: :array, minItems: 0) assert_validation [] assert_validation [1] schema :array, min_items: 1 assert_json(type: :array, minItems: 1) assert_validation [1] assert_validation [1, 2] assert_validation [] do error '/', 'Array has 0 items but needs at least 1.' end schema :array, min_items: 5 assert_json(type: :array, minItems: 5) assert_validation [1, 2, 3, 4] do error '/', 'Array has 4 items but needs at least 5.' end end def test_max_items schema :array, max_items: 0 assert_json(type: :array, maxItems: 0) assert_validation [] schema :array, max_items: 1 assert_json(type: :array, maxItems: 1) assert_validation [1] assert_validation [1, 2] do error '/', 'Array has 2 items but needs at most 1.' end schema :array, max_items: 5 assert_json(type: :array, maxItems: 5) assert_validation [1, 2, 3, 4, 5, 6] do error '/', 'Array has 6 items but needs at most 5.' end end def test_min_max_items schema :array, min_items: 2, max_items: 4 assert_json(type: :array, minItems: 2, maxItems: 4) assert_validation [1, 2] assert_validation [1, 2, 3] assert_validation [1, 2, 3, 4] assert_validation [1] do error '/', 'Array has 1 items but needs at least 2.' end assert_validation [1, 2, 3, 4, 5] do error '/', 'Array has 5 items but needs at most 4.' end end def test_unique_items schema :array, unique_items: true assert_json(type: :array, uniqueItems: true) assert_validation [1, 2] assert_validation [1, 2, :foo, 'bar', 'foo'] assert_validation [1, 1] do error '/', 'Array has duplicate items.' end assert_validation [:foo, :foo] do error '/', 'Array has duplicate items.' end assert_validation [1, 2, :foo, 'bar', 'foo'].as_json do error '/', 'Array has duplicate items.' end end def test_single_item_tuple schema :array do str end assert_json( type: :array, items: [ { type: :string } ], additionalItems: false ) assert_validation [] do error '/', 'Array has 0 items but must have exactly 1.' end assert_validation %w[foo] assert_validation %w[foo bar] do error '/', 'Array has 2 items but must have exactly 1.' end assert_validation ['foo', :bar] do error '/', 'Array has 2 items but must have exactly 1.' end assert_validation %i[foo] do error '/[0]', 'Invalid type, expected "string".' end end def test_multiple_item_tuple schema :array do str int hsh do str! :name end end assert_json( type: :array, items: [ { type: :string }, { type: :integer }, { type: :object, properties: { name: { type: :string } }, required: %i[name], additionalProperties: false } ], additionalItems: false ) assert_validation(['foo', 42, { name: 'Hello' }]) assert_validation [] do error '/', 'Array has 0 items but must have exactly 3.' end assert_validation ['foo'] do error '/', 'Array has 1 items but must have exactly 3.' end assert_validation([42, 42, { name: 'Hello' }]) do error '/[0]', 'Invalid type, expected "string".' end assert_validation(['foo', 42, { namex: 'Hello' }]) do error '/[2]/name', 'Value must be given.' error '/[2]', 'Obsolete property "namex".' end end def test_additional_items_true schema :array, additional_items: true do str int end assert_json( type: :array, items: [ { type: :string }, { type: :integer } ], additionalItems: true ) assert_validation(['foo', 42]) assert_validation(['foo', 42, 'additional']) assert_validation(['foo', 42, 42]) assert_cast(['Foo', 42], ['Foo', 42]) assert_cast(['Foo', 42, 42], ['Foo', 42, 42]) assert_cast(['Foo', 42, :bar], ['Foo', 42, :bar]) end def test_additional_items_true_casting schema :array, additional_items: true do str format: :date int end assert_json( type: :array, items: [ { type: :string, format: :date }, { type: :integer } ], additionalItems: true ) assert_validation(['1990-01-01', 42]) assert_validation(['1990-01-01', 42, 'additional']) assert_validation(['1990-01-01', 42, 42]) assert_cast(['1990-01-01', 42], [Date.new(1990, 1, 1), 42]) assert_cast(['1990-01-01', 42, '2010-01-01'], [Date.new(1990, 1, 1), 42, '2010-01-01']) assert_cast(['1990-01-01', 42, :bar], [Date.new(1990, 1, 1), 42, :bar]) end def test_additional_items_single schema :array do str add :integer end assert_json( type: :array, items: [ { type: :string } ], additionalItems: { type: :integer } ) assert_validation(['foo']) assert_validation(['foo', 42]) assert_validation(['foo', 42, 42]) assert_validation(['foo', :foo]) do error '/[1]', 'Invalid type, expected "integer".' end end def test_additional_items_schema schema :array, additional_items: true do str int add :string end assert_json( type: :array, items: [ { type: :string }, { type: :integer } ], additionalItems: { type: :string } ) assert_validation(['foo', 42]) assert_validation(['foo', 42, 'additional', 'another']) assert_validation(['foo', 42, 'additional', 42, 'another']) do error '/[3]', 'Invalid type, expected "string".' end assert_cast(['foo', 42], ['foo', 42]) assert_cast(['foo', 42, 'bar'], ['foo', 42, 'bar']) end def test_additional_items_schema_casting schema :array, additional_items: true do str int add :string, format: :date end assert_json( type: :array, items: [ { type: :string }, { type: :integer } ], additionalItems: { type: :string, format: :date } ) assert_validation(['foo', 42]) assert_validation(['foo', 42, '1990-01-01']) assert_validation(['foo', 42, '1990-01-01', 42]) do error '/[3]', 'Invalid type, expected "string".' end assert_validation(['foo', 42, '1990-01-01', 'foo']) do error '/[3]', 'String does not match format "date".' end assert_cast(['foo', 42], ['foo', 42]) assert_cast(['foo', 42, '1990-01-01'], ['foo', 42, Date.new(1990, 1, 1)]) end def test_additional_items_schema_oneof_casting schema :array, additional_items: true do str int add :one_of do str format: :date str format: :integer end end assert_json( type: :array, items: [ { type: :string }, { type: :integer } ], additionalItems: { oneOf: [ { type: :string, format: :date }, { type: :string, format: :integer } ] } ) assert_validation(['foo', 42]) assert_validation(['foo', 42, '1990-01-01']) assert_validation(['foo', 42, '1990-01-01', 42]) do error '/[3]', 'Matches 0 definitions but should match exactly 1.' end assert_validation(['foo', 42, '1990-01-01', 'foo']) do error '/[3]', 'Matches 0 definitions but should match exactly 1.' end assert_cast(['foo', 42], ['foo', 42]) assert_cast(['foo', 42, '1990-01-01'], ['foo', 42, Date.new(1990, 1, 1)]) assert_cast(['foo', 42, '1337'], ['foo', 42, 1337]) end def test_additional_items_schema_hash_casting schema :array, additional_items: true do str int add :hash do str! :foo, format: :date sym? :bar end end assert_json( type: :array, items: [ { type: :string }, { type: :integer } ], additionalItems: { properties: { foo: { type: :string, format: :date }, bar: {} }, additionalProperties: false, type: :object, required: [ :foo ] } ) assert_validation(['foo', 42]) assert_validation(['foo', 42, { foo: '1990-01-01' }]) assert_validation(['foo', 42, { foo: '1990-01-01', bar: :baz }]) assert_validation(['foo', 42, { foo: 1234 }]) do error '/[2]/foo', 'Invalid type, expected "string".' end assert_validation(['foo', 42, { foo: 'String' }]) do error '/[2]/foo', 'String does not match format "date".' end assert_cast(['foo', 42], ['foo', 42]) assert_cast(['foo', 42, { foo: '1990-01-01' }], ['foo', 42, { foo: Date.new(1990, 1, 1) }]) assert_cast(['foo', 42, { foo: '1990-01-01', bar: :baz }], ['foo', 42, { foo: Date.new(1990, 1, 1), bar: :baz }]) end def test_multiple_add_in_schema assert_raises_with_message Exceptions::InvalidSchemaError, 'You can only use "add" once to specify additional items.' do schema :array do add :integer add :string end end end def test_contains schema :array do cont :string end assert_json( type: :array, contains: { type: :string } ) assert_validation(%w[foo bar]) assert_validation(['foo', 42]) assert_validation(['foo']) assert_validation([]) do error '/', 'At least one entry must match schema {"type"=>"string"}.' end assert_validation([234, :foo]) do error '/', 'At least one entry must match schema {"type"=>"string"}.' end end def test_contains_int schema :array do cont :integer end assert_validation(['foo', 1, :bar]) assert_cast(['foo', 1, :bar], ['foo', 1, :bar]) end def test_contains_with_casting schema :array do cont :string, format: :date end assert_validation(nil) assert_validation(['1990-01-01']) assert_cast(['1990-01-01'], [Date.new(1990, 1, 1)]) assert_cast(%w[1990-01-01 123], [Date.new(1990, 1, 1), '123']) assert_cast(%w[1990-01-01 123 2010-01-01], [Date.new(1990, 1, 1), '123', Date.new(2010, 1, 1)]) end def test_defaults schema :array, default: [1, 2, 3] assert_cast nil, [1, 2, 3] assert_json( type: :array, default: [1, 2, 3] ) schema :array do hsh do str? :name, default: 'John' end end assert_json( type: :array, items: [ { type: :object, properties: { name: { type: :string, default: 'John' } }, additionalProperties: false } ], additionalItems: false ) assert_cast [{}], [{ name: 'John' }] end def test_enum_schema schema :array, enum: [1, 2, 'foo', :bar, { qux: 42 }, [1, 2], %w[a b]] assert_json({ type: :array, enum: [1, 2, 'foo', :bar, { qux: 42 }, [1, 2], %w[a b]] }) assert_validation(nil) assert_validation([1, 2]) assert_validation(%w[a b]) # Even we put those types in the enum, they need to fail the validations, # as they are not arrays assert_validation('foo') do error '/', 'Invalid type, expected "array".' end assert_validation(1) do error '/', 'Invalid type, expected "array".' end assert_validation(:bar) do error '/', 'Invalid type, expected "array".' end assert_validation({ qux: 42 }) do error '/', 'Invalid type, expected "array".' end # These need to fail validation, as they are not in the enum list assert_validation([1, 2, 3]) do error '/', 'Value not included in enum [1, 2, "foo", :bar, {:qux=>42}, [1, 2], ["a", "b"]].' end assert_validation([]) do error '/', 'Value not included in enum [1, 2, "foo", :bar, {:qux=>42}, [1, 2], ["a", "b"]].' end end def test_with_generic_keywords schema :array, enum: [1, 'foo', [1, 2, 3]], title: 'Array schema', description: 'Array schema holding generic keywords', examples: [ [1, 2, 3] ] assert_json({ type: :array, enum: [1, 'foo', [1, 2, 3]], title: 'Array schema', description: 'Array schema holding generic keywords', examples: [ [1, 2, 3] ] }) end # Helper function that checks for the min_items and max_items options if the option is # an integer or something else, in which case it needs to raise def validate_self_should_error(value_to_check) assert_raises_with_message Exceptions::InvalidSchemaError, 'Option "min_items" must be an "integer"' do schema :array, min_items: value_to_check end assert_raises_with_message Exceptions::InvalidSchemaError, 'Option "max_items" must be an "integer"' do schema :array, max_items: value_to_check end end def test_validate_self assert_raises_with_message Exceptions::InvalidSchemaError, 'Option "min_items" can\'t be greater than "max_items".' do schema :array, min_items: 5, max_items: 4 end assert_raises_with_message Exceptions::InvalidSchemaError, 'Option "unique_items" must be a "boolean".' do schema :array, unique_items: 4 end assert_raises_with_message Exceptions::InvalidSchemaError, 'Option "unique_items" must be a "boolean".' do schema :array, unique_items: 'false' end validate_self_should_error(1.0) validate_self_should_error(4r) validate_self_should_error(true) validate_self_should_error(false) validate_self_should_error(Object.new) validate_self_should_error((4 + 6i)) validate_self_should_error('13') validate_self_should_error('Lorem ipsum') end def test_list assert_raises_with_message Exceptions::InvalidSchemaError, 'You can only use "list" once.' do schema :array do list :integer list :symbol end end assert_raises_with_message Exceptions::InvalidSchemaError, 'Can\'t use "list" and normal items.' do schema :array do list :integer int end end assert_raises_with_message Exceptions::InvalidSchemaError, 'Can\'t use "list" and additional items.' do schema :array do list :integer add :symbol end end schema :array do list :integer end assert_validation([]) assert_validation([1]) assert_validation([1, 2, 3, 4, 5, 6]) assert_validation([1, :foo, 'bar']) do error '/[1]', 'Invalid type, expected "integer".' error '/[2]', 'Invalid type, expected "integer".' end end def test_list_casting schema :array do list :string, format: :date end assert_json( type: :array, items: { type: :string, format: :date } ) assert_validation(nil) assert_validation(['1990-01-01']) assert_validation(%w[1990-01-01 2020-01-01]) assert_validation(['foo']) do error '/[0]', 'String does not match format "date".' end assert_cast(['1990-01-01'], [Date.new(1990, 1, 1)]) end def test_simple_contains schema :array do cont :integer, minimum: 3 end assert_json( type: :array, contains: { type: :integer, minimum: 3 } ) assert_validation(nil) assert_validation([]) do error '/', 'At least one entry must match schema {"type"=>"integer", "minimum"=>3}.' end assert_validation([1, 2]) do error '/', 'At least one entry must match schema {"type"=>"integer", "minimum"=>3}.' end assert_validation([1, 2, 3]) end def test_contains_with_list schema :array do list :integer, minimum: 2 cont :integer, minimum: 5 end assert_json( type: :array, contains: { type: :integer, minimum: 5 }, items: { type: :integer, minimum: 2 } ) assert_validation(nil) assert_validation([]) do error '/', 'At least one entry must match schema {"type"=>"integer", "minimum"=>5}.' end assert_validation([1]) do error '/', 'At least one entry must match schema {"type"=>"integer", "minimum"=>5}.' error '/[0]', 'Value must have a minimum of 2.' end assert_validation([2]) do error '/', 'At least one entry must match schema {"type"=>"integer", "minimum"=>5}.' end assert_validation([2, 3, 5]) end def test_contains_need_casting schema :array do cont :string, format: :date end assert_json( type: :array, contains: { type: :string, format: :date } ) assert_validation(nil) assert_validation(['1990-01-01']) assert_validation(['1990-01-01', 1234]) assert_cast(['1990-01-01'], [Date.new(1990, 1, 1)]) assert_cast(%w[1990-01-01 123], [Date.new(1990, 1, 1), '123']) assert_cast(%w[1990-01-01 123 2010-01-01], [Date.new(1990, 1, 1), '123', Date.new(2010, 1, 1)]) end def test_contains_with_list_casting schema :array do list :string cont :string, format: :date end assert_json( type: :array, items: { type: :string }, contains: { type: :string, format: :date } ) assert_validation(nil) assert_validation(['foo']) do error '/', 'At least one entry must match schema {"type"=>"string", "format"=>"date"}.' end assert_validation(%w[foo 1990-01-01]) assert_cast(%w[foo 1990-01-01], ['foo', Date.new(1990, 1, 1)]) end end end end