#
# Copyright 2009-2013 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
#
# 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.
#
use strict;
use warnings;
use Test::More;
use Test::Fatal;
my $class = "MongoDB::_URI";
require_ok($class);
subtest "basic parsing" => sub {
my $uri =
new_ok( $class, [ uri => 'mongodb://user:pass@localhost/example_db?w=1' ] );
is( $uri->username, 'user' );
is( $uri->password, 'pass' );
my @hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids );
is( $uri->db_name, 'example_db' );
is( keys( %{ $uri->options } ), 1 );
is( $uri->options->{w}, 1 );
like( exception { $class->new( uri => 'invalid' ) }, qr/could not be parsed/ );
};
subtest "host list parsing" => sub {
my ( $uri, @hostids );
$uri = new_ok( $class, [ uri => 'mongodb://localhost' ] );
@hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids, "single hostname" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost,' ] );
@hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids, "single hostname with trailing comma" );
$uri = new_ok( $class, [ uri => 'mongodb://@localhost' ] );
@hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids, "hostname with empty auth credentials" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/' ] );
@hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids, "hostname with with trailing slash" );
$uri =
new_ok( $class, [ uri => 'mongodb://example1.com:27017,example2.com:27017' ] );
@hostids = ( 'example1.com:27017', 'example2.com:27017' );
is_deeply( $uri->hostids, \@hostids, "multiple hostnames" );
$uri =
new_ok( $class, [ uri => 'mongodb://localhost,localhost:27018,localhost:27019' ] );
@hostids = ( 'localhost:27017', 'localhost:27018', 'localhost:27019' );
is_deeply( $uri->hostids, \@hostids, "multiple hostnames at localhost" );
$uri = new_ok( $class,
[ uri => 'mongodb://localhost,example1.com:27018,localhost:27019' ] );
@hostids = ( 'localhost:27017', 'example1.com:27018', 'localhost:27019' );
is_deeply( $uri->hostids, \@hostids, "multiple hostnames (localhost/domain)" );
like(
exception { $class->new( uri => 'mongodb://' ) },
qr/missing host list/,
"missing host list"
);
like(
exception { $class->new( uri => 'mongodb:///' ) },
qr/missing host list/,
"missing host list, with trailing slash"
);
like(
exception { $class->new( uri => 'mongodb:///?' ) },
qr/missing host list/,
"missing host list, with trailing slash and question mark"
);
like(
exception { $class->new( uri => 'mongodb://local?host' ) },
qr/could not be parsed/,
"host list contains unescaped question mark"
);
};
subtest "hostname normalization and validation" => sub {
my ( $uri, @hostids );
$uri =
new_ok( $class, [ uri => 'mongodb://eXaMpLe1.cOm:27017,eXAMPLe2.com:27017' ] );
@hostids = ( 'example1.com:27017', 'example2.com:27017' );
is_deeply( $uri->hostids, \@hostids, "hostname normalized for case" );
$uri = new_ok( $class, [ uri => 'mongodb://local%68ost' ] );
@hostids = ('localhost:27017');
is_deeply( $uri->hostids, \@hostids, "hostname url decoded" );
$uri = new_ok( $class, [ uri => 'mongodb://hostwithembeddedc%6fmma' ] );
@hostids = ("hostwithembeddedcomma:27017");
is_deeply( $uri->hostids, \@hostids, "hostname can contain embedded comma" );
like(
exception { $class->new( uri => 'mongodb://:27017' ) },
qr/contains empty host/,
"empty hostname"
);
like(
exception { $class->new( uri => 'mongodb://@:27017' ) },
qr/contains empty host/,
"empty hostname, with leading at sign"
);
like(
exception { $class->new( uri => 'mongodb://%2Fa.sock' ) },
qr/Unix domain sockets are not supported/,
"unix domain socket (unsupported) with absolute path"
);
like(
exception { $class->new( uri => 'mongodb://a%2Fb.sock' ) },
qr/Unix domain sockets are not supported/,
"unix domain socket (unsupported) with relative path"
);
like(
exception { $class->new( uri => 'mongodb://a%2fb.sock' ) },
qr/Unix domain sockets are not supported/,
"unix domain socket (unsupported) with alternate URL encoding"
);
like(
exception { $class->new( uri => 'mongodb://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]' ) },
qr/IP literals are not supported/,
"ip literal (unsupported)"
);
like(
exception { $class->new( uri => 'mongodb://[::1]' ) },
qr/IP literals are not supported/,
"ip literal (unsupported), short localhost"
);
like(
exception { $class->new( uri => 'mongodb://[::1]:27017' ) },
qr/IP literals are not supported/,
"ip literal (unsupported) with port"
);
like(
exception { $class->new( uri => 'mongodb://[::1]:' ) },
qr/IP literals are not supported/,
"ip literal (unsupported) with empty port"
);
like(
exception { $class->new( uri => 'mongodb://localhost:/' ) },
qr/invalid port/,
"hostname with empty port"
);
like(
exception { $class->new( uri => 'mongodb://example.com:http' ) },
qr/invalid port/,
"non-numeric port"
);
like(
exception { $class->new( uri => 'mongodb://example.com:-1' ) },
qr/invalid port/,
"negative port"
);
like(
exception { $class->new( uri => 'mongodb://example.com:0' ) },
qr/invalid port.*must be in range/,
"port of 0"
);
like(
exception { $class->new( uri => 'mongodb://example.com:65536' ) },
qr/invalid port.*must be in range/,
"port of 65536"
);
};
subtest "db_name" => sub {
my $uri = new_ok( $class, [ uri => 'mongodb://localhost/example_db' ] );
is( $uri->db_name, 'example_db', "parse db_name" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost,/example_db' ] );
is( $uri->db_name, 'example_db', "parse db_name with trailing comma on host" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/example_db?' ] );
is( $uri->db_name, 'example_db', "parse db_name with trailing question mark" );
$uri = new_ok( $class,
[ uri => 'mongodb://localhost,localhost:27020,localhost:27021/example_db' ] );
is( $uri->db_name, 'example_db', "parse db_name, many hosts" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost' ] );
is( $uri->db_name, '', "no db_name with trailing ?" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/' ] );
is( $uri->db_name, '', "no db_name with trailing /" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?' ] );
is( $uri->db_name, '', "no db_name with trailing /?" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/a%62c' ] );
is( $uri->db_name, 'abc', "db_name properly unescaped" );
like(
exception { $class->new( uri => 'mongodb://localhost//' ) },
qr/database name must be URL encoded, found unescaped '\/'/,
"database with unescaped slash"
);
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?authSource=foo' ] );
is( $uri->db_name, 'foo', "parse db_name from authSource option" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/example_db?authSource=foo' ] );
is( $uri->db_name, 'foo', "parse db_name authSource override URI db_name" );
};
subtest "auth credentials" => sub {
my $uri;
$uri = new_ok( $class, [ uri => 'mongodb://fred:foobar@localhost' ] );
is( $uri->username, 'fred', "basic username parsing" );
is( $uri->password, 'foobar', "basic password parsing" );
$uri = new_ok( $class, [ uri => 'mongodb://fred@localhost' ] );
is( $uri->username, 'fred', "username when no password present" );
is( $uri->password, undef, "undefined password, when password not given" );
$uri = new_ok( $class, [ uri => 'mongodb://:@localhost' ] );
is( $uri->username, '', "empty username" );
is( $uri->password, '', "empty password" );
$uri = new_ok( $class, [ uri => 'mongodb://@localhost' ] );
is( $uri->username, '', "empty username, when password not given" );
is( $uri->password, undef,
"undefined password, when username empty and password not given" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost' ] );
is( $uri->username, undef, "undefined username, when no credentials given" );
is( $uri->password, undef, "undefined password, when no credentials given" );
$uri = new_ok( $class, [ uri => 'mongodb://dog%3Adogston:p%40ssword@localhost' ] );
is( $uri->username, 'dog:dogston', "percent encoded username" );
is( $uri->password, 'p@ssword', "percent encoded password" );
like(
exception { $class->new( uri => 'mongodb://user@name:password@localhost' ) },
qr/username must be URL encoded, found unescaped '\@'/,
'username with unescaped at sign'
);
like(
exception { $class->new( uri => 'mongodb://username:pass:word@localhost' ) },
qr/password must be URL encoded, found unescaped ':'/,
"password with unescaped colon"
);
};
subtest "options" => sub {
my ( $uri, @warnings, %expected_options );
$uri = new_ok( $class, [ uri => 'mongodb://localhost' ] );
%expected_options = ();
is_deeply( $uri->options, \%expected_options, "no options" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?' ] );
%expected_options = ();
is_deeply( $uri->options, \%expected_options, "no options with trailing question mark" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?&' ] );
%expected_options = ();
is_deeply( $uri->options, \%expected_options, "no options with trailing question mark and ampersand" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?w=1' ] );
%expected_options = (w => 1);
is_deeply( $uri->options, \%expected_options, "single option" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?w=1&replicaSet=set' ] );
%expected_options = (w => 1, replicaset => 'set');
is_deeply( $uri->options, \%expected_options, "multiple options" );
$uri = new_ok( $class,
[ uri => 'mongodb://localhost/?ReAdPrEfErEnCe=Primary&wTimeoutMS=1000' ] );
%expected_options = (readpreference => 'Primary', wtimeoutms => 1000);
is_deeply( $uri->options, \%expected_options, "multiple options with mixed case" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?%77=%74rue' ] );
%expected_options = (w => 'true');
is_deeply( $uri->options, \%expected_options, "percent encoded option" );
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?w=' ] );
%expected_options = (w => '');
is_deeply( $uri->options, \%expected_options, "options with empty option value" );
like(
exception { $class->new( uri => 'mongodb://localhost/?w' ) },
qr/expected key value pair/,
"no equals sign in option"
);
like(
exception { $class->new( uri => 'mongodb://localhost/?w==true' ) },
qr/expected key value pair/,
"extra equals signs at start of option value"
);
like(
exception { $class->new( uri => 'mongodb://localhost/?w=true=' ) },
qr/expected key value pair/,
"extra equals signs at end of option value"
);
like(
exception { $class->new( uri => 'mongodb://localhost/?w==true=' ) },
qr/expected key value pair/,
"equals signs at beginning and end of option value"
);
like(
exception { $class->new( uri => 'mongodb://localhost/?w==' ) },
qr/expected key value pair/,
"option value of equals sign"
);
@warnings = ();
{
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?unknown1=a&w=1&unknown2=b' ] );
}
is( scalar(@warnings), 2, "warnings for unknown options" );
is(
1,
scalar( grep /Unsupported option 'unknown1'/, @warnings ),
"warning for 'unknown1' option"
);
is(
1,
scalar( grep /Unsupported option 'unknown2'/, @warnings ),
"warning for 'unknown2' option"
);
%expected_options = (w => 1);
is_deeply( $uri->options, \%expected_options, "options with warnings" );
@warnings = ();
{
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
$uri = new_ok( $class, [ uri => 'mongodb://localhost/?w=1&w=2&replicaSet=set' ] );
}
is( scalar(@warnings), 1, "warnings for duplicate options" );
is(
1,
scalar( grep /Multiple options were found for the same value 'w'/, @warnings ),
"warning for duplicate option 'w'"
);
%expected_options = (w => 1, replicaset => 'set');
is_deeply( $uri->options, \%expected_options, "options with duplicate values" );
@warnings = ();
{
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
$uri = new_ok( $class,
[ uri => 'mongodb://localhost/?readPreferenceTags=a:b&readPreferenceTags=c:d' ] );
}
is( scalar(@warnings), 0, "no warning for duplicate option of list type" );
%expected_options = (readpreferencetags => [{a => 'b'}, {c => 'd'}]);
is_deeply( $uri->options, \%expected_options, "option of list type with duplicate values" );
};
done_testing;