The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

# Copyright 2015 - present MongoDB, Inc.
#
# Licensed 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
#
#
# 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.
use strict;
use Test::More 0.96;
use utf8;
use boolean;
use MongoDB;
use lib "t/lib";
use MongoDBTest qw/
build_client
get_capped
get_test_db
server_version
server_type
skip_unless_mongod
/;
skip_unless_mongod();
my $cb = MongoDBTest::Callback->new;
my $conn = build_client(
monitoring_callback => $cb->callback
);
my $testdb = get_test_db($conn);
my $server_version = server_version($conn);
my $server_type = server_type($conn);
my $coll = $testdb->get_collection('test_validating');
my $res;
my $does_validation = $server_version >= v3.1.3;
# only set up validation on servers that support it
sub _drop_coll {
$coll->drop;
$testdb->run_command( [ create => $coll->name ] );
if ($does_validation) {
$testdb->run_command(
[ collMod => $coll->name, validator => { x => { '$exists' => 1 } } ] );
}
pass("reset collection");
}
subtest "insert_one" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$cb->clear_events;
my $not_bypass = exception { $coll->insert_one( {}, { bypassDocumentValidation => 0 } )};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception { $coll->insert_one( {}, { bypassDocumentValidation => 1 } )};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "replace_one" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
my $id = $coll->insert_one( { x => 1 } )->inserted_id;
$cb->clear_events;
my $not_bypass = exception {
$coll->replace_one( { _id => $id }, { y => 1 }, { bypassDocumentValidation => 0 })
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->replace_one( { _id => $id }, { y => 1 }, { bypassDocumentValidation => 1 })
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "update_one" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$coll->insert_one( { x => 1 } );
$cb->clear_events;
my $not_bypass = exception {
$coll->update_one(
{ x => 1 },
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 0 }
)
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->update_one(
{ x => 1 },
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 1 }
)
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "update_many" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$coll->insert_many( [ { x => 1 }, { x => 2 } ] );
$cb->clear_events;
my $not_bypass = exception {
$coll->update_many(
{},
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 0 }
)
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->update_many(
{},
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 1 }
)
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest 'bulk_write (unordered)' => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
my $err;
$cb->clear_events;
my $not_bypass = exception {
$coll->bulk_write(
[ [ insert_one => [ { x => 1 } ] ], [ insert_many => [ {}, { x => 8 } ] ], ],
{ bypassDocumentValidation => 0, ordered => 0 },
);
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->bulk_write(
[ [ insert_one => [ { x => 1 } ] ], [ insert_many => [ {}, { x => 8 } ] ], ],
{ bypassDocumentValidation => 1, ordered => 0 },
);
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest 'bulk_write (ordered)' => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
my $err;
$cb->clear_events;
my $not_bypass = exception {
$coll->bulk_write(
[ [ insert_one => [ { x => 1 } ] ], [ insert_many => [ {}, { x => 8 } ] ], ],
{ bypassDocumentValidation => 0, ordered => 1 },
);
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->bulk_write(
[ [ insert_one => [ { x => 1 } ] ], [ insert_many => [ {}, { x => 8 } ] ], ],
{ bypassDocumentValidation => 1, ordered => 1 },
);
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
# insert_many uses bulk_write internally
subtest "insert_many" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$cb->clear_events;
my $not_bypass = exception {
$coll->insert_many( [ {}, {} ], { bypassDocumentValidation => 0 } )
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->insert_many( [ {}, {} ], { bypassDocumentValidation => 1 } )
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "find_one_and_replace" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$coll->insert_one( { x => 1 } );
$cb->clear_events;
my $not_bypass = exception {
$coll->find_one_and_replace( { x => 1 }, { y => 1 }, { bypassDocumentValidation => 0 } )
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->find_one_and_replace( { x => 1 }, { y => 1 }, { bypassDocumentValidation => 1 } )
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "find_one_and_update" => sub {
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
$coll->insert_one( { x => 1 } );
$cb->clear_events;
my $not_bypass = exception {
$coll->find_one_and_update(
{ x => 1 },
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 0 }
)
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception {
$coll->find_one_and_update(
{ x => 1 },
{ '$unset' => { x => 1 } },
{ bypassDocumentValidation => 1 }
)
};
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass , undef, "validation bypassed" );
};
subtest "aggregate with \$out" => sub {
plan skip_all => "Aggregation with \$out requires MongoDB 2.6+"
unless $server_version >= v2.6.0;
plan skip_all => "requires MongoDB 3.2+" unless $does_validation;
_drop_coll();
my $source = $testdb->get_collection('test_source');
$source->insert_many( [ map { { count => $_ } } 1 .. 20 ] );
$cb->clear_events;
my $not_bypass = exception {
$source->aggregate(
[ { '$match' => { count => { '$gt' => 10 } } }, { '$out' => $coll->name } ],
{ bypassDocumentValidation => 0 } )
};
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation not inserted when false');
like( $not_bypass, qr/failed validation/, "invalid when false on bypass" );
$cb->clear_events;
my $valid_bypass = exception { $source->aggregate(
[ { '$match' => { count => { '$gt' => 10 } } }, { '$out' => $coll->name } ],
{ bypassDocumentValidation => 1 } ) };
my $event = $cb->events->[-2];
ok(exists $event->{ command }->{ 'bypassDocumentValidation' },
'bypassDocumentValidation inserted when true');
is( $valid_bypass, undef, "validation bypassed" );
is( $coll->count_documents({}), 10, "correct doc count in \$out collection" );
is(
exception {
$source->aggregate(
[ { '$match' => { count => { '$gt' => 10 } } } ],
{ bypassDocumentValidation => 1 } );
},
undef,
"bypassDocumentValidation without \$out",
);
};
subtest bypass_validation_default => sub {
my $source = $testdb->get_collection('test_source');
# For this test we don't use validation on the collection. We don't
# care whether it works or not, only that without a bypass option,
# we don't see a 'bypassDocumentValidation' option sent to the server.
$coll->drop;
my %cases = (
insert_one => sub { $coll->insert_one( {} ) },
replace_one => sub { $coll->replace_one( { _id => 123 }, { y => 1 } ) },
update_one => sub { $coll->update_one( { x => 1 }, { '$unset' => { x => 1 } } ) },
update_many => sub { $coll->update_many( {}, { '$unset' => { x => 1 } } ) },
bulk_write => sub {
$coll->bulk_write(
[ [ insert_one => [ { x => 1 } ] ], [ insert_many => [ {}, { x => 8 } ] ] ] );
},
insert_many => sub { $coll->insert_many( [ {}, {} ], ) },
fine_one_and_replace => sub { $coll->find_one_and_replace( { x => 1 }, { y => 1 } ) },
find_one_and_update => sub { $coll->find_one_and_update( { x => 1 }, { '$unset' => { x => 1 } } ) },
aggregate_with_out => sub {
$source->aggregate(
[ { '$match' => { count => { '$gt' => 10 } } }, { '$out' => $coll->name } ] );
},
);
for my $k ( sort keys %cases ) {
$cb->clear_events;
$cases{$k}->();
my $err_event = $cb->events->[-2];
ok( !exists $err_event->{ command }->{ 'bypassDocumentValidation' },
"$k: bypassDocumentValidation not inserted when default undef");
}
};
done_testing;