# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'test_help' class TestSchema < Test::Unit::TestCase def validate!(schema, value) Avro::SchemaValidator.validate!(schema, value) end def hash_to_schema(hash) Avro::Schema.parse(hash.to_json) end def assert_failed_validation(messages) error = assert_raise(Avro::SchemaValidator::ValidationError) { yield } assert_messages = [messages].flatten result_errors = error.result.errors assert_messages.each do |message| assert(result_errors.include?(message), "expected '#{message}' to be in '#{result_errors}'") end assert_equal(assert_messages.size, result_errors.size) end def assert_valid_schema(schema, valid, invalid) valid.each do |value| assert_nothing_raised { Avro::SchemaValidator.validate!(schema, value) } end invalid.each do |value| assert_raise { Avro::SchemaValidator.validate!(schema, value) } end end def test_validate_nil schema = hash_to_schema(type: 'null', name: 'name') assert_nothing_raised { validate!(schema, nil) } assert_failed_validation('at . expected type null, got int with value 1') do validate!(schema, 1) end end def test_validate_boolean schema = hash_to_schema(type: 'boolean', name: 'name') assert_nothing_raised { validate!(schema, true) } assert_nothing_raised { validate!(schema, false) } assert_failed_validation('at . expected type boolean, got int with value 1') do validate!(schema, 1) end assert_failed_validation('at . expected type boolean, got null') do validate!(schema, nil) end end def test_fixed_size_string schema = hash_to_schema(type: 'fixed', name: 'some', size: 3) assert_nothing_raised { validate!(schema, 'baf') } assert_failed_validation('at . expected fixed with size 3, got "some" with size 4') do validate!(schema, 'some') end assert_failed_validation('at . expected fixed with size 3, got null') do validate!(schema, nil) end end def test_original_validate_nil schema = hash_to_schema(type: 'null', name: 'name') assert_valid_schema(schema, [nil], ['something']) end def test_original_validate_boolean schema = hash_to_schema(type: 'boolean', name: 'name') assert_valid_schema(schema, [true, false], [nil, 1]) end def test_validate_string schema = hash_to_schema(type: 'string', name: 'name') assert_valid_schema(schema, ['string'], [nil, 1]) end def test_validate_bytes schema = hash_to_schema(type: 'bytes', name: 'name') assert_valid_schema(schema, ['string'], [nil, 1]) end def test_validate_int schema = hash_to_schema(type: 'int', name: 'name') assert_valid_schema( schema, [Avro::Schema::INT_MIN_VALUE, Avro::Schema::INT_MAX_VALUE, 1], [Avro::Schema::LONG_MIN_VALUE, Avro::Schema::LONG_MAX_VALUE, 'string'] ) assert_failed_validation('at . out of bound value 9223372036854775807') do validate!(schema, Avro::Schema::LONG_MAX_VALUE) end end def test_validate_long schema = hash_to_schema(type: 'long', name: 'name') assert_valid_schema(schema, [Avro::Schema::LONG_MIN_VALUE, Avro::Schema::LONG_MAX_VALUE, 1], [1.1, 'string']) end def test_validate_float schema = hash_to_schema(type: 'float', name: 'name') assert_valid_schema(schema, [1.1, 1, Avro::Schema::LONG_MAX_VALUE], ['string']) end def test_validate_double schema = hash_to_schema(type: 'double', name: 'name') assert_valid_schema(schema, [1.1, 1, Avro::Schema::LONG_MAX_VALUE], ['string']) end def test_validate_fixed schema = hash_to_schema(type: 'fixed', name: 'name', size: 3) assert_valid_schema(schema, ['abc'], ['ab', 1, 1.1, true]) end def test_validate_original_num schema = hash_to_schema(type: 'enum', name: 'name', symbols: %w(a b)) assert_valid_schema(schema, ['a', 'b'], ['c']) end def test_validate_record schema = hash_to_schema(type: 'record', name: 'name', fields: [{ type: 'null', name: 'sub' }]) assert_valid_schema(schema, [{ 'sub' => nil }], [{ 'sub' => 1 }]) end def test_validate_shallow_record schema = hash_to_schema( type: 'record', name: 'name', fields: [{ type: 'int', name: 'sub' }] ) assert_nothing_raised { validate!(schema, 'sub' => 1) } assert_failed_validation('at .sub expected type int, got null') do validate!(schema, {}) end assert_failed_validation('at . expected type record, got float with value 1.2') do validate!(schema, 1.2) end assert_failed_validation('at .sub expected type int, got float with value 1.2') do validate!(schema, 'sub' => 1.2) end assert_failed_validation('at .sub expected type int, got null') do validate!(schema, {}) end end def test_validate_array schema = hash_to_schema(type: 'array', name: 'person', items: [{ type: 'int', name: 'height' }]) assert_nothing_raised { validate!(schema, []) } assert_failed_validation 'at . expected type array, got null' do validate!(schema, nil) end assert_failed_validation('at .[0] expected type int, got null') do validate!(schema, [nil]) end assert_failed_validation('at .[3] expected type int, got string with value "so wrong"') do validate!(schema, [1, 3, 9, 'so wrong']) end end def test_validate_enum schema = hash_to_schema(type: 'enum', name: 'person', symbols: %w(one two three)) assert_nothing_raised { validate!(schema, 'one') } assert_failed_validation('at . expected enum with values ["one", "two", "three"], got string with value "five"') do validate!(schema, 'five') end end def test_validate_union_on_primitive_types schema = hash_to_schema( name: 'should_not_matter', type: 'record', fields: [ { name: 'what_ever', type: %w(long string) } ] ) assert_failed_validation('at .what_ever expected union of [\'long\', \'string\'], got null') { validate!(schema, 'what_ever' => nil) } end def test_validate_union_of_nil_and_record_inside_array schema = hash_to_schema( name: 'this does not matter', type: 'record', fields: [ { name: 'person', type: { name: 'person_entry', type: 'record', fields: [ { name: 'houses', type: [ 'null', { name: 'houses_entry', type: 'array', items: [ { name: 'house_entry', type: 'record', fields: [ { name: 'number_of_rooms', type: 'long' } ] } ] } ], } ] } } ] ) assert_failed_validation('at .person expected type record, got null') { validate!(schema, 'not at all' => nil) } assert_nothing_raised { validate!(schema, 'person' => {}) } assert_nothing_raised { validate!(schema, 'person' => { houses: [] }) } assert_nothing_raised { validate!(schema, 'person' => { 'houses' => [{ 'number_of_rooms' => 1 }] }) } message = 'at .person.houses[1].number_of_rooms expected type long, got string with value "not valid at all"' assert_failed_validation(message) do validate!( schema, 'person' => { 'houses' => [ { 'number_of_rooms' => 2 }, { 'number_of_rooms' => 'not valid at all' } ] } ) end end def test_validate_map schema = hash_to_schema(type: 'map', name: 'numbers', values: [ { name: 'some', type: 'int' } ]) assert_nothing_raised { validate!(schema, 'some' => 1) } assert_failed_validation('at .some expected type int, got string with value "nope"') do validate!(schema, 'some' => 'nope') end assert_failed_validation("at . unexpected key type 'Symbol' in map") do validate!(schema, some: 1) end end def test_validate_deep_record schema = hash_to_schema(type: 'record', name: 'person', fields: [ { name: 'head', type: { name: 'head', type: 'record', fields: [ { name: 'hair', type: { name: 'hair', type: 'record', fields: [ { name: 'color', type: 'string' } ] } } ] } } ]) assert_nothing_raised { validate!(schema, 'head' => { 'hair' => { 'color' => 'black' } }) } assert_failed_validation('at .head.hair.color expected type string, got null') do validate!(schema, 'head' => { 'hair' => { 'color' => nil } }) end assert_failed_validation('at .head.hair.color expected type string, got null') do validate!(schema, 'head' => { 'hair' => {} }) end assert_failed_validation('at .head.hair expected type record, got null') do validate!(schema, 'head' => {}) end assert_failed_validation('at . expected type record, got null') do validate!(schema, nil) end end def test_validate_deep_record_with_array schema = hash_to_schema(type: 'record', name: 'fruits', fields: [ { name: 'fruits', type: { name: 'fruits', type: 'array', items: [ { name: 'fruit', type: 'record', fields: [ { name: 'name', type: 'string' }, { name: 'weight', type: 'float' } ] } ] } } ]) assert_nothing_raised { validate!(schema, 'fruits' => [{ 'name' => 'apple', 'weight' => 30.2 }]) } assert_failed_validation('at .fruits[0].name expected type string, got null') do validate!(schema, 'fruits' => [{ 'name' => nil, 'weight' => 30.2 }]) end assert_failed_validation('at .fruits expected type array, got int with value 1') do validate!(schema, 'fruits' => 1) end end def test_validate_multiple_errors schema = hash_to_schema(type: 'array', name: 'ages', items: [ { type: 'int', name: 'age' } ]) exception = assert_raise(Avro::SchemaValidator::ValidationError) do validate!(schema, [nil, 'e']) end assert_equal 2, exception.result.errors.size assert_equal( "at .[0] expected type int, got null\nat .[1] expected type int, got string with value \"e\"", exception.to_s ) end end