feat: add full Zonemaster stack with Docker and Spanish UI
- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI) - Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend - Dockerfile.gui: Astro static build served via nginx - docker-compose.yml: backend (internal) + frontend (port 5353) - nginx.conf: root redirects to /es/, /api/ proxied to backend - zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
12
zonemaster-backend/t/00-load.t
Normal file
12
zonemaster-backend/t/00-load.t
Normal file
@@ -0,0 +1,12 @@
|
||||
use 5.014002;
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use Test::More;
|
||||
|
||||
plan tests => 1;
|
||||
|
||||
BEGIN {
|
||||
use_ok( 'Zonemaster::Backend::Config' ) || print "Bail out!\n";
|
||||
}
|
||||
|
||||
done_testing;
|
||||
197
zonemaster-backend/t/TestUtil.pm
Normal file
197
zonemaster-backend/t/TestUtil.pm
Normal file
@@ -0,0 +1,197 @@
|
||||
package TestUtil;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::More;
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Backend::Config;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
TestUtil - a set of methods to ease Zonemaster::Backend unit testing
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Because this package lies in the testing folder C<t/> and that folder is
|
||||
unknown to the include path @INC, it can be including using the following code:
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil;
|
||||
|
||||
Explicitely load any dependencies to Zonemaster::Backend::RPCAPI or
|
||||
Zonemaster::Backend::TestAgent modules with
|
||||
|
||||
use TestUtil qw( RPCAPI TestAgent );
|
||||
|
||||
=head1 ENVIRONMENT
|
||||
|
||||
=head2 TARGET
|
||||
|
||||
Set the database to use.
|
||||
Can be C<SQLite>, C<MySQL> or C<PostgreSQL>.
|
||||
Default to C<SQLite>.
|
||||
|
||||
=head2 ZONEMASTER_RECORD
|
||||
|
||||
If set, the data from the test is recorded to a file. Otherwise the data is
|
||||
loaded from a file.
|
||||
|
||||
=cut
|
||||
|
||||
# Use the TARGET environment variable to set the database to use
|
||||
# default to SQLite
|
||||
my $db_backend = Zonemaster::Backend::Config->check_db( $ENV{TARGET} || 'SQLite' );
|
||||
note "database: $db_backend";
|
||||
|
||||
sub import {
|
||||
my ( $class, @args ) = @_;
|
||||
if ( grep { $_ eq 'RPCAPI' } @args ) {
|
||||
require Zonemaster::Backend::RPCAPI;
|
||||
Zonemaster::Backend::RPCAPI->import();
|
||||
}
|
||||
if ( grep { $_ eq 'TestAgent' } @args ) {
|
||||
require Zonemaster::Backend::TestAgent;
|
||||
Zonemaster::Backend::TestAgent->import();
|
||||
}
|
||||
}
|
||||
|
||||
sub db_backend {
|
||||
return $db_backend;
|
||||
}
|
||||
|
||||
sub restore_datafile {
|
||||
my ( $datafile ) = @_;
|
||||
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine->preload_cache( $datafile );
|
||||
Zonemaster::Engine->profile->set( q{no_network}, 1 );
|
||||
} else {
|
||||
diag "recording";
|
||||
}
|
||||
}
|
||||
|
||||
sub save_datafile {
|
||||
my ( $datafile ) = @_;
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine->save_cache( $datafile );
|
||||
}
|
||||
}
|
||||
|
||||
sub prepare_db {
|
||||
my ( $db ) = @_;
|
||||
|
||||
$db->drop_tables();
|
||||
$db->create_schema();
|
||||
}
|
||||
|
||||
sub init_db {
|
||||
my ( $config ) = @_;
|
||||
|
||||
my $dbclass = Zonemaster::Backend::DB->get_db_class( $db_backend );
|
||||
my $db = $dbclass->from_config( $config );
|
||||
|
||||
prepare_db( $db );
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
sub create_rpcapi {
|
||||
my ( $config ) = @_;
|
||||
|
||||
my $rpcapi;
|
||||
eval {
|
||||
$rpcapi = Zonemaster::Backend::RPCAPI->new(
|
||||
{
|
||||
dbtype => $db_backend,
|
||||
config => $config,
|
||||
}
|
||||
);
|
||||
};
|
||||
if ( $@ ) {
|
||||
diag explain( $@ );
|
||||
BAIL_OUT( 'Could not connect to database' );
|
||||
}
|
||||
|
||||
if ( not $rpcapi->isa('Zonemaster::Backend::RPCAPI' ) ) {
|
||||
BAIL_OUT( 'Not a Zonemaster::Backend::RPCAPI object' );
|
||||
}
|
||||
|
||||
prepare_db( $rpcapi->{db} );
|
||||
|
||||
return $rpcapi;
|
||||
}
|
||||
|
||||
sub create_testagent {
|
||||
my ( $config ) = @_;
|
||||
|
||||
my $agent = Zonemaster::Backend::TestAgent->new(
|
||||
{
|
||||
dbtype => "$db_backend",
|
||||
config => $config
|
||||
}
|
||||
);
|
||||
|
||||
if ( not $agent->isa('Zonemaster::Backend::TestAgent' ) ) {
|
||||
BAIL_OUT( 'Not a Zonemaster::Backend::TestAgent object' );
|
||||
}
|
||||
|
||||
return $agent;
|
||||
}
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item db_backend()
|
||||
|
||||
Returns the name of the currently used database engine. This value is set via
|
||||
the TARGET environment variable.
|
||||
|
||||
=item restore_datafile($datafile)
|
||||
|
||||
If the ZONEMASTER_RECORD environment variable is unset, the data from
|
||||
C<$datafile> is used for all the current tests.
|
||||
|
||||
=item save_datafile($datafile)
|
||||
|
||||
If the ZONEMASTER_RECORD environment variable is set, the data from the current
|
||||
tests are stored to C<$datafile>.
|
||||
|
||||
=item prepare_db($db)
|
||||
|
||||
Recreate all tables anew for the associated C<$db>.
|
||||
|
||||
=item init_db($config)
|
||||
|
||||
Returns a new Zonemaster::Backend::DB object using the provided C<$config>
|
||||
file.
|
||||
|
||||
Database tables are dropped and created anew.
|
||||
|
||||
=item create_rpcapi($config)
|
||||
|
||||
Returns a new Zonemaster::Backend::RPCAPI object using the provided C<$config>
|
||||
file.
|
||||
|
||||
Database tables are dropped and created anew.
|
||||
|
||||
=item create_testagent($config)
|
||||
|
||||
Returns a new Zonemaster::Backend::TestAgent object using the provided
|
||||
C<$config> file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
361
zonemaster-backend/t/batches.t
Normal file
361
zonemaster-backend/t/batches.t
Normal file
@@ -0,0 +1,361 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Temp qw[tempdir];
|
||||
use POSIX qw( strftime );
|
||||
use Time::Local qw( timelocal_modern );
|
||||
use Test::Exception;
|
||||
use Test::More; # see done_testing()
|
||||
use Test::Differences;
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil qw( RPCAPI );
|
||||
|
||||
use Zonemaster::Backend::Config;
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $config = <<EOF;
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = en_US
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
test_profile=$t_path/test_profile.json
|
||||
EOF
|
||||
|
||||
my $user = {
|
||||
username => 'user',
|
||||
api_key => 'key'
|
||||
};
|
||||
|
||||
# define the default properties for the tests
|
||||
my $params = {
|
||||
client_id => 'Unit Test',
|
||||
client_version => '1.0',
|
||||
ipv4 => JSON::PP::true,
|
||||
ipv6 => JSON::PP::true,
|
||||
profile => 'test_profile',
|
||||
};
|
||||
|
||||
# Create Zonemaster::Backend::RPCAPI object
|
||||
sub init_backend {
|
||||
my ( $config ) = @_;
|
||||
|
||||
my $rpcapi = TestUtil::create_rpcapi( $config );
|
||||
|
||||
# create a user
|
||||
$rpcapi->add_api_user( $user );
|
||||
|
||||
return $rpcapi;
|
||||
}
|
||||
|
||||
sub to_timestamp {
|
||||
my ( $date ) = @_;
|
||||
|
||||
my ( $year, $month, $day, $hour, $min, $sec ) = split( /[\s:-]+/, $date );
|
||||
my $time = timelocal_modern( $sec, $min, $hour, $day, $month-1, $year );
|
||||
return $time;
|
||||
}
|
||||
|
||||
sub check_tolerance {
|
||||
my ( $ref_time, $msg ) = @_;
|
||||
|
||||
my $current_time = strftime "%Y-%m-%d %H:%M:%S", gmtime( time() );
|
||||
my $delta = abs( to_timestamp($current_time) - to_timestamp($ref_time) );
|
||||
|
||||
my $tolerance = 60; # 1 minute is tolerable between ret_time and current_time
|
||||
|
||||
cmp_ok( $delta, '<=', $tolerance, $msg);
|
||||
}
|
||||
|
||||
subtest 'RPCAPI add_batch_job' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
my $dbh = $rpcapi->{db}->dbh;
|
||||
|
||||
my @domains = ( 'afnic.fr' );
|
||||
|
||||
my $res = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $res, 1, 'correct batch job id returned' );
|
||||
|
||||
subtest 'table "batch_jobs" contains an entry' => sub {
|
||||
my ( $count ) = $dbh->selectrow_array( q[ SELECT count(*) FROM batch_jobs ] );
|
||||
is( $count, 1, 'one row in table' );
|
||||
|
||||
my ( $id, $username, $created_at ) = $dbh->selectrow_array( q[ SELECT * FROM batch_jobs ]);
|
||||
is( $id, 1, 'first batch id is 1' );
|
||||
is( $username, $user->{username}, 'correct batch user' );
|
||||
ok( $created_at, 'defined creation time' );
|
||||
check_tolerance( $created_at, 'creation time in tolerance zone' );
|
||||
};
|
||||
|
||||
subtest 'table "test_results" contains an entry' => sub {
|
||||
my ( $count ) = $dbh->selectrow_array( q[ SELECT count(*) FROM test_results ] );
|
||||
is( $count, 1, 'one row in table' );
|
||||
|
||||
my ( $hash_id, $domain, $batch_id, $created_at, $started_at, $ended_at, $params ) = $dbh->selectrow_array(
|
||||
q[
|
||||
SELECT
|
||||
hash_id,
|
||||
domain,
|
||||
batch_id,
|
||||
created_at,
|
||||
started_at,
|
||||
ended_at,
|
||||
params
|
||||
FROM test_results
|
||||
]
|
||||
);
|
||||
|
||||
is( length($hash_id), 16, 'correct hash_id length' );
|
||||
is( $domain, $domains[0], 'correct domain' );
|
||||
is( $batch_id, 1, 'correct batch_id' );
|
||||
ok( $created_at, 'defined creation time' );
|
||||
check_tolerance( $created_at, 'creation time in tolerance zone' );
|
||||
ok( ! defined $started_at, 'undefined start time' );
|
||||
ok( ! defined $ended_at, 'undefined end time' );
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'RPCAPI batch_status' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
subtest 'batch job exists' => sub {
|
||||
my @domains = ( 'afnic.fr' );
|
||||
|
||||
my $batch_id = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $batch_id, 1, 'correct batch job id returned' );
|
||||
|
||||
my $res = $rpcapi->batch_status( { batch_id => $batch_id } );
|
||||
is( $res->{waiting_count}, scalar @domains, 'correct number of runninng tests' );
|
||||
is( $res->{running_count}, 0, 'correct number of finished tests' );
|
||||
is( $res->{finished_count}, 0, 'correct number of finished tests' );
|
||||
ok( !exists $res->{waiting_tests}, 'list of waiting tests expected to be absent' );
|
||||
ok( !exists $res->{running_tests}, 'list of running tests expected to be absent' );
|
||||
ok( !exists $res->{finished_tests}, 'list of finished tests to be absent' );
|
||||
|
||||
};
|
||||
|
||||
subtest 'unknown batch (batch_status)' => sub {
|
||||
my $unknown_batch = 10;
|
||||
dies_ok {
|
||||
$rpcapi->batch_status( { batch_id => $unknown_batch } );
|
||||
} 'getting results for an unknown batch_id should die';
|
||||
my $res = $@;
|
||||
is( $res->{error}, 'Zonemaster::Backend::Error::ResourceNotFound', 'correct error type' );
|
||||
is( $res->{message}, 'Unknown batch', 'correct error message' );
|
||||
is( $res->{data}->{batch_id}, $unknown_batch, 'correct data type returned' );
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'batch with several domains' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
my $dbh = $rpcapi->{db}->dbh;
|
||||
|
||||
my @domains = sort( 'afnic.fr', 'iis.se' );
|
||||
|
||||
my $res = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $res, 1, 'correct batch job id returned' );
|
||||
|
||||
# No lists of test IDs requested
|
||||
$res = $rpcapi->batch_status( { batch_id => 1 } );
|
||||
|
||||
is( $res->{waiting_count}, scalar @domains, 'correct number of running tests' );
|
||||
is( $res->{running_count}, 0, 'correct number of finished tests' );
|
||||
is( $res->{finished_count}, 0, 'correct number of finished tests' );
|
||||
ok( !exists $res->{waiting_tests}, 'list of waiting tests expected to be absent' );
|
||||
ok( !exists $res->{running_tests}, 'list of running tests expected to be absent' );
|
||||
ok( !exists $res->{finished_tests}, 'list of finished tests expected to be absent' );
|
||||
|
||||
# List of waiting test IDs requested
|
||||
$res = $rpcapi->batch_status( { batch_id => 1, list_waiting_tests => 1 } );
|
||||
|
||||
is( $res->{waiting_count}, scalar @domains, 'correct number of runninng tests' );
|
||||
is( $res->{running_count}, 0, 'correct number of finished tests' );
|
||||
is( $res->{finished_count}, 0, 'correct number of finished tests' );
|
||||
is( scalar @{ $res->{waiting_tests} }, scalar @domains, 'correct number of elements in waiting_tests' );
|
||||
ok( !exists $res->{running_tests}, 'list of running tests expected to be absent' );
|
||||
ok( !exists $res->{finished_tests}, 'list of finished tests expected to be absent' );
|
||||
|
||||
subtest 'table "test_results" contains 2 entries' => sub {
|
||||
my ( $count ) = $dbh->selectrow_array( q[ SELECT count(*) FROM test_results ] );
|
||||
is( $count, @domains, 'two rows in table' );
|
||||
|
||||
my $rows = $dbh->selectall_hashref(
|
||||
q[
|
||||
SELECT
|
||||
hash_id,
|
||||
domain,
|
||||
batch_id,
|
||||
created_at,
|
||||
started_at,
|
||||
ended_at,
|
||||
params
|
||||
FROM test_results
|
||||
],
|
||||
'domain'
|
||||
);
|
||||
|
||||
my @keys = sort keys %$rows;
|
||||
is_deeply( \@keys, \@domains, 'correct domains' );
|
||||
|
||||
foreach my $domain ( @keys ) {
|
||||
is( length($rows->{$domain}->{hash_id}), 16, "[$domain] correct hash_id length" );
|
||||
is( $rows->{$domain}->{batch_id}, 1, "[$domain] correct batch_id" );
|
||||
ok( $rows->{$domain}->{created_at}, "[$domain] defined creation time" );
|
||||
check_tolerance( $rows->{$domain}->{created_at}, "[$domain] creation time in tolerance zone" );
|
||||
ok( ! defined $rows->{$domain}->{started_at}, "[$domain] undefined start time" );
|
||||
ok( ! defined $rows->{$domain}->{ended_at}, "[$domain] undefined end time" );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'batch job still running' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
my $dbh = $rpcapi->{db}->dbh;
|
||||
|
||||
my @domains = ( 'afnic.fr' );
|
||||
|
||||
my $batch_id = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $batch_id, 1, 'correct batch job id returned' );
|
||||
|
||||
|
||||
subtest 'a batch is already running for the user, new batch creation should not fail' => sub {
|
||||
my $batch_id = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $batch_id, 2, 'same user can create another batch' );
|
||||
};
|
||||
|
||||
subtest 'use another user' => sub {
|
||||
my $another_user = { username => 'another', api_key => 'token' };
|
||||
$rpcapi->add_api_user( $another_user );
|
||||
my $batch_id = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$another_user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
is( $batch_id, 3, 'another_user can create another batch' );
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'duplicate user should fail' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
|
||||
# do not output any error message
|
||||
my $printerror_before = $rpcapi->{db}->dbh->{PrintError};
|
||||
$rpcapi->{db}->dbh->{PrintError} = 0;
|
||||
|
||||
dies_ok {
|
||||
$rpcapi->add_api_user( { username => $user->{username}, api_key => "another api key" } );
|
||||
} 'a user with the same username already exists, add_api_user should die';
|
||||
my $res = $@;
|
||||
is( $res->{error}, 'Zonemaster::Backend::Error::Conflict', 'correct error type' );
|
||||
is( $res->{message}, 'User already exists', 'correct error message' );
|
||||
is( $res->{data}->{username}, $user->{username}, 'correct data type returned' );
|
||||
|
||||
# reset attribute value
|
||||
$rpcapi->{db}->dbh->{PrintError} = $printerror_before;
|
||||
};
|
||||
|
||||
subtest 'normalize "domain" column' => sub {
|
||||
my $config = Zonemaster::Backend::Config->parse( $config );
|
||||
my $rpcapi = init_backend( $config );
|
||||
my $dbh = $rpcapi->{db}->dbh;
|
||||
|
||||
my %domains_to_test = (
|
||||
"aFnIc.Fr" => "afnic.fr",
|
||||
"afnic.fr." => "afnic.fr",
|
||||
"aFnic.Fr." => "afnic.fr"
|
||||
);
|
||||
my @domains = keys %domains_to_test;
|
||||
|
||||
my $batch_id = $rpcapi->add_batch_job(
|
||||
{
|
||||
%$user,
|
||||
domains => \@domains,
|
||||
test_params => $params
|
||||
}
|
||||
);
|
||||
|
||||
my @db_domain = map { $$_[0] } $dbh->selectall_array( "SELECT domain FROM test_results WHERE batch_id=?", undef, $batch_id );
|
||||
|
||||
is( @db_domain, 3, '3 tests created' );
|
||||
my @expected = values %domains_to_test;
|
||||
is_deeply( \@db_domain, \@expected, 'domains are normalized' );
|
||||
};
|
||||
|
||||
# TODO: create an agent and run batch tests
|
||||
|
||||
## Create the agent
|
||||
#use_ok( 'Zonemaster::Backend::TestAgent' );
|
||||
#my $agent = Zonemaster::Backend::TestAgent->new( { dbtype => "$db_backend", config => $config } );
|
||||
#isa_ok($agent, 'Zonemaster::Backend::TestAgent', 'agent');
|
||||
|
||||
done_testing();
|
||||
891
zonemaster-backend/t/config.t
Normal file
891
zonemaster-backend/t/config.t
Normal file
@@ -0,0 +1,891 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
use Test::Differences;
|
||||
use Test::Exception;
|
||||
use Log::Any::Test; # Must come before use Log::Any
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
use File::Slurp qw( read_file );
|
||||
use File::Spec::Functions qw( catfile );
|
||||
use Log::Any qw( $log );
|
||||
|
||||
subtest 'Everything but NoWarnings' => sub {
|
||||
|
||||
use_ok( 'Zonemaster::Backend::Config' );
|
||||
|
||||
subtest 'Set values' => sub {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = sqlite
|
||||
polling_interval = 1.5
|
||||
|
||||
[MYSQL]
|
||||
host = mysql-host
|
||||
port = 3456
|
||||
user = mysql_user
|
||||
password = mysql_password
|
||||
database = mysql_database
|
||||
|
||||
[POSTGRESQL]
|
||||
host = postgresql-host
|
||||
port = 6543
|
||||
user = postgresql_user
|
||||
password = postgresql_password
|
||||
database = postgresql_database
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = sv_FI
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
default = /path/to/default.profile
|
||||
two = /path/to/two.profile
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
three = /path/to/three.profile
|
||||
four = /path/to/four.profile
|
||||
|
||||
[ZONEMASTER]
|
||||
max_zonemaster_execution_time = 1200
|
||||
number_of_processes_for_frontend_testing = 30
|
||||
number_of_processes_for_batch_testing = 40
|
||||
lock_on_queue = 1
|
||||
age_reuse_previous_test = 800
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
isa_ok $config, 'Zonemaster::Backend::Config', 'parse() return value';
|
||||
is $config->DB_engine, 'SQLite', 'set: DB.engine';
|
||||
is $config->DB_polling_interval, 1.5, 'set: DB.polling_interval';
|
||||
is $config->MYSQL_host, 'mysql-host', 'set: MYSQL.host';
|
||||
is $config->MYSQL_port, 3456, 'set: MYSQL.port';
|
||||
is $config->MYSQL_user, 'mysql_user', 'set: MYSQL.user';
|
||||
is $config->MYSQL_password, 'mysql_password', 'set: MYSQL.password';
|
||||
is $config->MYSQL_database, 'mysql_database', 'set: MYSQL.database';
|
||||
is $config->POSTGRESQL_host, 'postgresql-host', 'set: POSTGRESQL.host';
|
||||
is $config->POSTGRESQL_port, 6543, 'set: POSTGRESQL.port';
|
||||
is $config->POSTGRESQL_user, 'postgresql_user', 'set: POSTGRESQL.user';
|
||||
is $config->POSTGRESQL_password, 'postgresql_password', 'set: POSTGRESQL.password';
|
||||
is $config->POSTGRESQL_database, 'postgresql_database', 'set: POSTGRESQL.database';
|
||||
is $config->SQLITE_database_file, '/var/db/zonemaster.sqlite', 'set: SQLITE.database_file';
|
||||
eq_or_diff { $config->LANGUAGE_locale }, { sv => 'sv_FI' }, 'set: LANGUAGE.locale';
|
||||
eq_or_diff { $config->PUBLIC_PROFILES }, { #
|
||||
default => '/path/to/default.profile',
|
||||
two => '/path/to/two.profile'
|
||||
},
|
||||
'set: PUBLIC PROFILES';
|
||||
eq_or_diff { $config->PRIVATE_PROFILES }, { #
|
||||
three => '/path/to/three.profile',
|
||||
four => '/path/to/four.profile'
|
||||
},
|
||||
'set: PRIVATE PROFILES';
|
||||
is $config->ZONEMASTER_max_zonemaster_execution_time, 1200, 'set: ZONEMASTER.max_zonemaster_execution_time';
|
||||
is $config->ZONEMASTER_number_of_processes_for_frontend_testing, 30, 'set: ZONEMASTER.number_of_processes_for_frontend_testing';
|
||||
is $config->ZONEMASTER_number_of_processes_for_batch_testing, 40, 'set: ZONEMASTER.number_of_processes_for_batch_testing';
|
||||
is $config->ZONEMASTER_lock_on_queue, 1, 'set: ZONEMASTER.lock_on_queue';
|
||||
is $config->ZONEMASTER_age_reuse_previous_test, 800, 'set: ZONEMASTER.age_reuse_previous_test';
|
||||
};
|
||||
|
||||
subtest 'Default values' => sub {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
cmp_ok abs( $config->DB_polling_interval - 0.5 ), '<', 0.000001, 'default: DB.polling_interval';
|
||||
is $config->MYSQL_port, 3306, 'default: MYSQL.port';
|
||||
is $config->POSTGRESQL_port, 5432, 'default: POSTGRESQL.port';
|
||||
eq_or_diff { $config->LANGUAGE_locale }, { en => 'en_US' }, 'default: LANGUAGE.locale';
|
||||
eq_or_diff { $config->PUBLIC_PROFILES }, { default => undef }, 'default: PUBLIC_PROFILES';
|
||||
eq_or_diff { $config->PRIVATE_PROFILES }, {}, 'default: PRIVATE_PROFILES';
|
||||
is $config->ZONEMASTER_max_zonemaster_execution_time, 600, 'default: ZONEMASTER.max_zonemaster_execution_time';
|
||||
is $config->ZONEMASTER_number_of_processes_for_frontend_testing, 20, 'default: ZONEMASTER.number_of_processes_for_frontend_testing';
|
||||
is $config->ZONEMASTER_number_of_processes_for_batch_testing, 20, 'default: ZONEMASTER.number_of_processes_for_batch_testing';
|
||||
is $config->ZONEMASTER_lock_on_queue, 0, 'default: ZONEMASTER.lock_on_queue';
|
||||
is $config->ZONEMASTER_age_reuse_previous_test, 600, 'default: ZONEMASTER.age_reuse_previous_test';
|
||||
|
||||
is $config->RPCAPI_enable_add_api_user, 0, 'default: RPCAPI.enable_add_api_user';
|
||||
is $config->RPCAPI_enable_add_batch_job, 1, 'default: RPCAPI.enable_add_batch_job';
|
||||
};
|
||||
|
||||
SKIP: {
|
||||
skip "no more deprecated values", 1;
|
||||
|
||||
subtest 'Deprecated values and fallbacks that are unconditional' => sub {
|
||||
$log->clear();
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
};
|
||||
}
|
||||
|
||||
subtest 'Warnings' => sub {
|
||||
$log->clear();
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
port = 3333
|
||||
user = mysql_user
|
||||
password = mysql_password
|
||||
database = mysql_database
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
$log->contains_ok( qr/MYSQL\.port.*MYSQL\.host/, 'warning: MYSQL.host is "localhost" and MYSQL.port defined' );
|
||||
is $config->MYSQL_host, 'localhost', 'set: MYSQL.host';
|
||||
is $config->MYSQL_port, 3333, 'set: MYSQL.port';
|
||||
};
|
||||
|
||||
throws_ok {
|
||||
$log->clear();
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
[LANGUAGE]
|
||||
locale =
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/Use of empty LANGUAGE.locale property is not permitted/, 'die: Invalid empty locale tag';
|
||||
|
||||
throws_ok {
|
||||
my $text = '{"this":"is","not":"a","valid":"ini","file":"!"}';
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/Failed to parse config/, 'die: Invalid INI format';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = Excel
|
||||
|
||||
[SQLITE]
|
||||
databse_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[ZNMEOTAESR]
|
||||
lock_on_queue = 1
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{section.*ZNMEOTAESR}, 'die: Invalid section name';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
pnlilog_iatnvrel = 0.5
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{property.*pnlilog_iatnvrel}, 'die: Invalid property name';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = Excel
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/DB\.engine.*Excel/, 'die: Invalid DB.engine value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
polling_interval = hourly
|
||||
|
||||
[SQLITE]
|
||||
databse_file = /var/db/zonemaster.sqlite
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{DB\.polling_interval.*hourly}, 'die: Invalid DB.polling_interval value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = 192.0.2.1:3306
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{MYSQL\.host.*192.0.2.1:3306}, 'die: Invalid MYSQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = Robert'); DROP TABLE Students;--
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{MYSQL\.user.*Robert'\); DROP TABLE Students;--}, 'die: Invalid MYSQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster
|
||||
password = (╯°□°)╯︵ ┻━┻
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{MYSQL\.password.*\(╯°□°\)╯︵ ┻━┻}, 'die: Invalid MYSQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = |)/-\'|'/-\|3/-\$[-
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{MYSQL\.database.*|\)/-\'|'/-\\|3/-\\$[-}, 'die: Invalid MYSQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = 192.0.2.1:5432
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{POSTGRESQL\.host.*192.0.2.1:5432}, 'die: Invalid POSTGRESQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = Robert'); DROP TABLE Students;--
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{POSTGRESQL\.user.*Robert'\); DROP TABLE Students;--}, 'die: Invalid POSTGRESQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster
|
||||
password = (╯°□°)╯︵ ┻━┻
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{POSTGRESQL\.password.*\(╯°□°\)╯︵ ┻━┻}, 'die: Invalid POSTGRESQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = |)/-\'|'/-\|3/-\$[-
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{POSTGRESQL\.database.*|\)/-\'|'/-\\|3/-\\$[-}, 'die: Invalid POSTGRESQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = ./relative/path/to/zonemaster.sqlite
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{SQLITE\.database_file.*\./relative/path/to/zonemaster.sqlite}, 'die: Invalid SQLITE.database_file value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[ZONEMASTER]
|
||||
max_zonemaster_execution_time = 0
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{ZONEMASTER\.max_zonemaster_execution_time.*0}, 'die: Invalid ZONEMASTER.max_zonemaster_execution_time value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[ZONEMASTER]
|
||||
lock_on_queue = -1
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{ZONEMASTER\.lock_on_queue.*-1}, 'die: Invalid ZONEMASTER.lock_on_queue value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[ZONEMASTER]
|
||||
number_of_processes_for_frontend_testing = 0
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{ZONEMASTER\.number_of_processes_for_frontend_testing.*0}, 'die: Invalid ZONEMASTER.number_of_processes_for_frontend_testing value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[ZONEMASTER]
|
||||
number_of_processes_for_batch_testing = 100000
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{ZONEMASTER\.number_of_processes_for_batch_testing.*100000}, 'die: Invalid ZONEMASTER.number_of_processes_for_batch_testing value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[ZONEMASTER]
|
||||
age_reuse_previous_test = 0
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr{ZONEMASTER\.age_reuse_previous_test.*0}, 'die: Invalid ZONEMASTER.age_reuse_previous_test value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.host/, 'die: Missing MYSQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.user/, 'die: Missing MYSQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.password/, 'die: Missing MYSQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.database/, 'die: Missing MYSQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.host/, 'die: Missing POSTGRESQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.user/, 'die: Missing POSTGRESQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.password/, 'die: Missing POSTGRESQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.database/, 'die: Missing POSTGRESQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.host/, 'die: Missing MYSQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.user/, 'die: Missing MYSQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.password/, 'die: Missing MYSQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = MySQL
|
||||
|
||||
[MYSQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/MYSQL\.database/, 'die: Missing MYSQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.host/, 'die: Missing POSTGRESQL.host value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
password = zonemaster_password
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.user/, 'die: Missing POSTGRESQL.user value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
database = zonemaster_database
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.password/, 'die: Missing POSTGRESQL.password value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = PostgreSQL
|
||||
|
||||
[POSTGRESQL]
|
||||
host = zonemaster-host
|
||||
user = zonemaster_user
|
||||
password = zonemaster_password
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/POSTGRESQL\.database/, 'die: Missing POSTGRESQL.database value';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = English
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/LANGUAGE\.locale.*English/, 'die: Invalid locale_tag in LANGUAGE.locale';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = en_GB en_US
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/LANGUAGE\.locale.*en/, 'die: Repeated language code in LANGUAGE.locale';
|
||||
|
||||
lives_and {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
DEFAULT = /path/to/my.profile
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
SECRET = /path/to/my.profile
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
eq_or_diff { $config->PUBLIC_PROFILES }, { default => '/path/to/my.profile' }, 'normalize profile names under PUBLIC PROFILES';
|
||||
eq_or_diff { $config->PRIVATE_PROFILES }, { secret => '/path/to/my.profile' }, 'normalize profile names under PRIVATE PROFILES';
|
||||
};
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
-invalid-name- = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/PUBLIC PROFILES.*-invalid-name-/, 'die: Invalid profile name in PUBLIC PROFILES';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
-invalid-name- = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/PRIVATE PROFILES.*-invalid-name-/, 'die: Invalid profile name in PRIVATE PROFILES';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
valid-name = relative/path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/absolute.*valid-name/, 'die: Invalid absolute path in PUBLIC PROFILES';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
valid-name = relative/path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/absolute.*valid-name/, 'die: Invalid absolute path in PRIVATE PROFILES';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
valid-name = /path/to/my.profile
|
||||
valid-name = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/unique.*valid-name/, 'die: Repeated profile name in PUBLIC PROFILES section';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
valid-name = /path/to/my.profile
|
||||
valid-name = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/unique.*valid-name/, 'die: Repeated profile name in PRIVATE PROFILES section';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
pub-and-priv = /path/to/my.profile
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
pub-and-priv = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/unique.*pub-and-priv/, 'die: Repeated profile name across sections';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[PRIVATE PROFILES]
|
||||
default = /path/to/my.profile
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/PRIVATE PROFILES.*default/, 'die: Default profile in PRIVATE PROFILES';
|
||||
|
||||
subtest 'RPCAPI experimental aliases' => sub {
|
||||
subtest 'default values' => sub {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
is $config->RPCAPI_enable_add_api_user, 0, 'default: RPCAPI.enable_add_api_user';
|
||||
is $config->RPCAPI_enable_add_batch_job, 1, 'default: RPCAPI.enable_add_batch_job';
|
||||
is $config->RPCAPI_enable_user_create, 0, 'default: RPCAPI.enable_user_create';
|
||||
is $config->RPCAPI_enable_batch_create, 1, 'default: RPCAPI.enable_batch_create';
|
||||
};
|
||||
|
||||
subtest 'specifying stable and experimental parameters is forbidden' => sub {
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[RPCAPI]
|
||||
enable_user_create = no
|
||||
enable_add_api_user = yes
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/Error:.+RPCAPI\.enable_add_api_user.+RPCAPI\.enable_user_create/, 'die: RPCAPI stable and experimental alias (add_api_user/user_create)';
|
||||
|
||||
throws_ok {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[RPCAPI]
|
||||
enable_add_batch_job = no
|
||||
enable_batch_create = no
|
||||
};
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
}
|
||||
qr/Error:.+RPCAPI\.enable_add_batch_job.+RPCAPI\.enable_batch_create/, 'die: RPCAPI stable and experimental alias (batch_job/batch_create)';
|
||||
};
|
||||
|
||||
subtest 'setting alias' => sub {
|
||||
my $text = q{
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = /var/db/zonemaster.sqlite
|
||||
|
||||
[RPCAPI]
|
||||
enable_user_create = no
|
||||
enable_batch_create = no
|
||||
};
|
||||
my $config = Zonemaster::Backend::Config->parse( $text );
|
||||
is $config->RPCAPI_enable_user_create, 0, 'set: RPCAPI.enable_user_create';
|
||||
is $config->RPCAPI_enable_batch_create, 0, 'set: RPCAPI.enable_batch_create';
|
||||
is $config->RPCAPI_enable_add_api_user, 0, 'aliased: RPCAPI.enable_add_api_user';
|
||||
is $config->RPCAPI_enable_add_batch_job, 0, 'aliased: RPCAPI.enable_add_batch_job';
|
||||
};
|
||||
};
|
||||
|
||||
{
|
||||
my $path = catfile( dirname( $0 ), '..', 'share', 'backend_config.ini' );
|
||||
my $text = read_file( $path );
|
||||
lives_ok {
|
||||
Zonemaster::Backend::Config->parse( $text );
|
||||
} 'default config is valid';
|
||||
}
|
||||
|
||||
};
|
||||
221
zonemaster-backend/t/db.t
Normal file
221
zonemaster-backend/t/db.t
Normal file
@@ -0,0 +1,221 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Encode;
|
||||
use Test::More; # see done_testing()
|
||||
|
||||
use_ok( 'Zonemaster::Backend::DB' );
|
||||
|
||||
sub encode_and_fingerprint {
|
||||
my $params = shift;
|
||||
|
||||
my $self = "Zonemaster::Backend::DB";
|
||||
my $encoded_params = $self->encode_params( $params );
|
||||
my $fingerprint = $self->generate_fingerprint( $params );
|
||||
|
||||
return ( $encoded_params, $fingerprint );
|
||||
}
|
||||
|
||||
subtest 'encoding and fingerprint' => sub {
|
||||
|
||||
subtest 'missing properties' => sub {
|
||||
my %params = ( domain => "example.com" );
|
||||
|
||||
my $expected_encoded_params = '{"domain":"example.com","ds_info":[],"ipv4":null,"ipv6":null,"nameservers":[],"profile":"default"}';
|
||||
my ( $encoded_params, $fingerprint ) = encode_and_fingerprint( \%params );
|
||||
is $encoded_params, $expected_encoded_params, 'domain only: the encoded strings should match';
|
||||
#diag ($fingerprint);
|
||||
|
||||
my $expected_encoded_params_v4_true = '{"domain":"example.com","ds_info":[],"ipv4":true,"ipv6":null,"nameservers":[],"profile":"default"}';
|
||||
$params{ipv4} = JSON::PP->true;
|
||||
my ( $encoded_params_ipv4, $fingerprint_ipv4 ) = encode_and_fingerprint( \%params );
|
||||
is $encoded_params_ipv4, $expected_encoded_params_v4_true, 'add ipv4: the encoded strings should match';
|
||||
isnt $fingerprint_ipv4, $fingerprint, 'fingerprints should not match';
|
||||
};
|
||||
|
||||
subtest 'array properties' => sub {
|
||||
subtest 'ds_info' => sub {
|
||||
my %params1 = (
|
||||
domain => "example.com",
|
||||
ds_info => [{
|
||||
algorithm => 8,
|
||||
keytag => 11627,
|
||||
digtype => 2,
|
||||
digest => "a6cca9e6027ecc80ba0f6d747923127f1d69005fe4f0ec0461bd633482595448"
|
||||
}]
|
||||
);
|
||||
my %params2 = (
|
||||
ds_info => [{
|
||||
digtype => 2,
|
||||
algorithm => 8,
|
||||
keytag => 11627,
|
||||
digest => "a6cca9e6027ecc80ba0f6d747923127f1d69005fe4f0ec0461bd633482595448"
|
||||
}],
|
||||
domain => "example.com"
|
||||
);
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
is $fingerprint1, $fingerprint2, 'ds_info same fingerprint';
|
||||
is $encoded_params1, $encoded_params2, 'ds_info same encoded string';
|
||||
};
|
||||
|
||||
subtest 'nameservers order' => sub {
|
||||
my %params1 = (
|
||||
domain => "example.com",
|
||||
nameservers => [
|
||||
{ ns => "ns2.nic.fr", ip => "192.134.4.1" },
|
||||
{ ns => "ns1.nic.fr" },
|
||||
{ ip => "192.0.2.1", ns => "ns3.nic.fr"}
|
||||
]
|
||||
);
|
||||
my %params2 = (
|
||||
nameservers => [
|
||||
{ ns => "ns3.nic.fr", ip => "192.0.2.1" },
|
||||
{ ns => "ns1.nic.fr" },
|
||||
{ ip => "192.134.4.1", ns => "ns2.nic.fr"}
|
||||
],
|
||||
domain => "example.com"
|
||||
);
|
||||
my %params3 = (
|
||||
domain => "example.com",
|
||||
nameservers => [
|
||||
{ ip => "", ns => "ns1.nic.fr" },
|
||||
{ ns => "ns3.nic.FR", ip => "192.0.2.1" },
|
||||
{ ns => "ns2.nic.fr", ip => "192.134.4.1" }
|
||||
]
|
||||
);
|
||||
my %params4 = (
|
||||
domain => "example.com",
|
||||
nameservers => [
|
||||
{ ip => "192.134.4.1", ns => "nS2.Nic.FR"},
|
||||
{ ns => "Ns1.nIC.fR", ip => "" },
|
||||
{ ns => "ns3.nic.fr", ip => "192.0.2.1" }
|
||||
]
|
||||
);
|
||||
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
my ( $encoded_params3, $fingerprint3 ) = encode_and_fingerprint( \%params3 );
|
||||
my ( $encoded_params4, $fingerprint4 ) = encode_and_fingerprint( \%params4 );
|
||||
|
||||
is $fingerprint1, $fingerprint2, 'nameservers: same fingerprint';
|
||||
is $encoded_params1, $encoded_params2, 'nameservers: same encoded string';
|
||||
|
||||
is $fingerprint1, $fingerprint3, 'nameservers: same fingerprint (empty ip)';
|
||||
is $encoded_params1, $encoded_params3, 'nameservers: same encoded string (empty ip)';
|
||||
|
||||
is $fingerprint1, $fingerprint4, 'nameservers: same fingerprint (ignore nameservers\' ns case)';
|
||||
is $encoded_params1, $encoded_params4, 'nameservers: same encoded string (ignore nameservers\' ns case)';
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'should be case insensitive' => sub {
|
||||
my %params1 = ( domain => "example.com" );
|
||||
my %params2 = ( domain => "eXamPLe.COm" );
|
||||
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
is $fingerprint1, $fingerprint2, 'same fingerprint';
|
||||
is $encoded_params1, $encoded_params2, 'same encoded string';
|
||||
};
|
||||
|
||||
subtest 'garbage properties set' => sub {
|
||||
my $expected_encoded_params = '{"client":"GUI v3.3.0","domain":"example.com","ds_info":[],"ipv4":null,"ipv6":null,"nameservers":[],"profile":"default"}';
|
||||
my %params1 = (
|
||||
domain => "example.com",
|
||||
);
|
||||
my %params2 = (
|
||||
domain => "example.com",
|
||||
client => "GUI v3.3.0"
|
||||
);
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
|
||||
is $fingerprint1, $fingerprint2, 'leave out garbage property in fingerprint computation...';
|
||||
is $encoded_params2, $expected_encoded_params, '...but keep it in the encoded string';
|
||||
};
|
||||
|
||||
subtest 'should have different fingerprints' => sub {
|
||||
subtest 'different profiles' => sub {
|
||||
my %params1 = (
|
||||
domain => "example.com",
|
||||
profile => "profile_1"
|
||||
);
|
||||
my %params2 = (
|
||||
domain => "example.com",
|
||||
profile => "profile_2"
|
||||
);
|
||||
my ( undef, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( undef, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
|
||||
isnt $fingerprint1, $fingerprint2, 'different profiles, different fingerprints';
|
||||
};
|
||||
subtest 'different IP protocols' => sub {
|
||||
my %params1 = (
|
||||
domain => "example.com",
|
||||
ipv4 => "true",
|
||||
ipv6 => "false"
|
||||
);
|
||||
my %params2 = (
|
||||
domain => "example.com",
|
||||
ipv4 => "false",
|
||||
ipv6 => "true"
|
||||
);
|
||||
my ( undef, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( undef, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
|
||||
isnt $fingerprint1, $fingerprint2, 'different IP protocols, different fingerprints';
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'IDN domain' => sub {
|
||||
my $expected_encoded_params = encode_utf8( '{"domain":"xn--caf-dma.example","ds_info":[],"ipv4":true,"ipv6":true,"nameservers":[],"profile":"default"}' );
|
||||
my $expected_fingerprint = '8cb027ff2c175f48aed2623abad0cdd2';
|
||||
|
||||
my %params = ( domain => "café.example" );
|
||||
$params{ipv4} = JSON::PP->true;
|
||||
$params{ipv6} = JSON::PP->true;
|
||||
|
||||
my ( $encoded_params, $fingerprint ) = encode_and_fingerprint( \%params );
|
||||
is $encoded_params, $expected_encoded_params, 'IDN domain: the encoded strings should match';
|
||||
is $fingerprint, $expected_fingerprint, 'IDN domain: correct fingerprint';
|
||||
};
|
||||
|
||||
subtest 'final dots' => sub {
|
||||
subtest 'in domain' => sub {
|
||||
my %params1 = ( domain => "example.com" );
|
||||
my %params2 = ( domain => "example.com." );
|
||||
my $expected_encoded_params = encode_utf8( '{"domain":"example.com","ds_info":[],"ipv4":null,"ipv6":null,"nameservers":[],"profile":"default"}' );
|
||||
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
is $fingerprint1, $fingerprint2, 'same fingerprint';
|
||||
is $encoded_params1, $expected_encoded_params, 'the encoded strings should match';
|
||||
|
||||
};
|
||||
|
||||
subtest 'in nameserver' => sub {
|
||||
my %params1 = ( domain => "example.com", nameservers => [ { ns => "ns1.example.com." } ] );
|
||||
my %params2 = ( domain => "example.com", nameservers => [ { ns => "ns1.example.com" } ] );
|
||||
my $expected_encoded_params = encode_utf8( '{"domain":"example.com","ds_info":[],"ipv4":null,"ipv6":null,"nameservers":[{"ns":"ns1.example.com"}],"profile":"default"}' );
|
||||
|
||||
my ( $encoded_params1, $fingerprint1 ) = encode_and_fingerprint( \%params1 );
|
||||
my ( $encoded_params2, $fingerprint2 ) = encode_and_fingerprint( \%params2 );
|
||||
is $fingerprint1, $fingerprint2, 'same fingerprint';
|
||||
is $encoded_params1, $expected_encoded_params, 'the encoded strings should match';
|
||||
|
||||
};
|
||||
|
||||
subtest 'root is not modified' => sub {
|
||||
my %params = ( domain => "." );
|
||||
my $expected_encoded_params = encode_utf8( '{"domain":".","ds_info":[],"ipv4":null,"ipv6":null,"nameservers":[],"profile":"default"}' );
|
||||
|
||||
my ( $encoded_params, $fingerprint ) = encode_and_fingerprint( \%params );
|
||||
is $encoded_params, $expected_encoded_params, 'the encoded strings should match';
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
done_testing();
|
||||
150
zonemaster-backend/t/db_ddl.t
Normal file
150
zonemaster-backend/t/db_ddl.t
Normal file
@@ -0,0 +1,150 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::Exception;
|
||||
use Test::NoWarnings qw(warnings clear_warnings);
|
||||
|
||||
use File::ShareDir qw[dist_file];
|
||||
use File::Temp qw[tempdir];
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil;
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Backend::Config;
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[ZONEMASTER]
|
||||
age_reuse_previous_test = 10
|
||||
EOF
|
||||
|
||||
my $dbclass = Zonemaster::Backend::DB->get_db_class( $db_backend );
|
||||
my $db = $dbclass->from_config( $config );
|
||||
|
||||
|
||||
subtest 'Everything but Test::NoWarnings' => sub {
|
||||
|
||||
subtest 'drop and create' => sub {
|
||||
subtest 'first drop (cleanup) ... ' => sub {
|
||||
$db->drop_tables();
|
||||
dies_ok {
|
||||
$db->dbh->do( 'SELECT 1 FROM test_results' )
|
||||
}
|
||||
'table "test_results" sould not exist';
|
||||
};
|
||||
subtest '... then drop after create ...' => sub {
|
||||
$db->create_schema();
|
||||
my ( $res ) = $db->dbh->selectrow_array( 'SELECT count(*) FROM test_results' );
|
||||
is $res, 0, 'a. after create, table "test_results" should exist and be empty';
|
||||
|
||||
$db->drop_tables();
|
||||
dies_ok {
|
||||
$db->dbh->do( 'SELECT 1 FROM test_results' )
|
||||
}
|
||||
'b. after drop, table "test_results" sould be removed';
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'constraints' => sub {
|
||||
$db->create_schema();
|
||||
|
||||
subtest 'constraint unique' => sub {
|
||||
my $time = $db->format_time( time() );
|
||||
my @constraints = (
|
||||
{
|
||||
table => 'test_results',
|
||||
key => 'hash_id',
|
||||
sql => "INSERT INTO test_results (hash_id,domain,created_at,params)
|
||||
VALUES ('0123456789abcdef', 'domain.test', '$time', '{}')"
|
||||
},
|
||||
{
|
||||
table => 'log_level',
|
||||
key => 'level',
|
||||
sql => "INSERT INTO log_level (level, value) VALUES ('OTHER', 10)"
|
||||
},
|
||||
{
|
||||
table => 'users',
|
||||
key => 'username',
|
||||
sql => "INSERT INTO users (username) VALUES ('user1')"
|
||||
},
|
||||
);
|
||||
|
||||
for my $c (@constraints) {
|
||||
$db->dbh->do( $c->{sql} );
|
||||
throws_ok {
|
||||
$db->dbh->do( $c->{sql} );
|
||||
}
|
||||
qr/(unique constraint|duplicate entry)/i, "$c->{table}($c->{key}) key should be unique";
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'constraint on foreign key' => sub {
|
||||
subtest 'result_entries - hash_id should exist in test_results(hash_id)' => sub {
|
||||
my $hash_id_ok = "0123456789abcdef";
|
||||
# INFO is 1
|
||||
my $sql = "INSERT INTO result_entries (hash_id, level, module, testcase, tag, timestamp, args)
|
||||
VALUES ('$hash_id_ok', 1, 'MODULE', 'TESTCASE', 'TAG', 42, '{}')";
|
||||
my $inserted_rows = $db->dbh->do( $sql );
|
||||
is $inserted_rows, 1, 'can insert an entry with an existing hash_id';
|
||||
|
||||
throws_ok {
|
||||
my $hash_id_ko = "aaaaaaaaaaaaaaaa";
|
||||
my $sql = "INSERT INTO result_entries (hash_id, level, module, testcase, tag, timestamp, args)
|
||||
VALUES ('$hash_id_ko', 1, 'MODULE', 'TESTCASE', 'TAG', 42, '{}')";
|
||||
$db->dbh->do( $sql );
|
||||
}
|
||||
qr/foreign key/i, 'cannot insert an entry with an non-existing hash_id';
|
||||
};
|
||||
|
||||
subtest 'result_entries - level should exist in log_level(level)' => sub {
|
||||
my $level = 1; # INFO
|
||||
my $sql = "INSERT INTO result_entries (hash_id, level, module, testcase, tag, timestamp, args)
|
||||
VALUES ('0123456789abcdef', '$level', 'MODULE', 'TESTCASE', 'TAG', 42, '{}')";
|
||||
my $inserted_rows = $db->dbh->do( $sql );
|
||||
is $inserted_rows, 1, 'can insert an entry with an existing level';
|
||||
|
||||
throws_ok {
|
||||
my $level = 42; # does not exist
|
||||
my $sql = "INSERT INTO result_entries (hash_id, level, module, testcase, tag, timestamp, args)
|
||||
VALUES ('0123456789abcdef', '$level', 'MODULE', 'TESTCASE', 'TAG', 42, '{}')";
|
||||
$db->dbh->do( $sql );
|
||||
}
|
||||
qr/foreign key/i, 'cannot insert an entry with an non-existing level';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# FIXME: hack to avoid getting warnings from Test::NoWarnings
|
||||
my @warn = warnings();
|
||||
if ( @warn == 7 ) {
|
||||
clear_warnings();
|
||||
}
|
||||
26
zonemaster-backend/t/idn.data
Normal file
26
zonemaster-backend/t/idn.data
Normal file
@@ -0,0 +1,26 @@
|
||||
i.root-servers.net 192.36.148.17 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"192.36.148.17","querytime":10,"timestamp":1646935543.48935,"data":"+U2EAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
i.root-servers.net 2001:07fe:0000:0000:0000:0000:0000:0053 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"zQCEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.51238,"answerfrom":"2001:7fe::53","querytime":9}}}}
|
||||
d.root-servers.net 199.7.91.13 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.15536,"data":"QraEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","querytime":14,"answerfrom":"199.7.91.13"}}}}
|
||||
d.root-servers.net 2001:0500:002d:0000:0000:0000:0000:000d {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":10,"answerfrom":"2001:500:2d::d","timestamp":1646935543.18271,"data":"nQOEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
c.root-servers.net 2001:0500:0002:0000:0000:0000:0000:000c {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"7VmEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.1185,"answerfrom":"2001:500:2::c","querytime":23}}}}
|
||||
c.root-servers.net 192.33.4.12 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":24,"answerfrom":"192.33.4.12","timestamp":1646935543.08159,"data":"+EmEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
j.root-servers.net 2001:0503:0c27:0000:0000:0000:0002:0030 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.54733,"data":"bdyEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","answerfrom":"2001:503:c27::2:30","querytime":10}}}}
|
||||
j.root-servers.net 192.58.128.30 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.53484,"data":"8oWEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","answerfrom":"192.58.128.30","querytime":2}}}}
|
||||
b.root-servers.net 2001:0500:0200:0000:0000:0000:0000:000b {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"2001:500:200::b","querytime":13,"data":"rtKEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.05494}}}}
|
||||
b.root-servers.net 199.9.14.201 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"eSqEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.02769,"querytime":14,"answerfrom":"199.9.14.201"}}}}
|
||||
l.root-servers.net 2001:0500:009f:0000:0000:0000:0000:0042 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"3tWEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.63711,"answerfrom":"2001:500:9f::42","querytime":10}}}}
|
||||
l.root-servers.net 199.7.83.42 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.61372,"data":"LXeEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","querytime":10,"answerfrom":"199.7.83.42"}}}}
|
||||
m.root-servers.net 2001:0dc3:0000:0000:0000:0000:0000:0035 {"WcLt/i2sUcZA//eE56F52g":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":3,"answerfrom":"2001:dc3::35","timestamp":1646935543.86015,"data":"LDiEAwABAAAAAQAAB2V4YW1wbGUAAAYAAQAABgABAAAAAABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}},"5e42wXPdot60bOvWyxbkKQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.85123,"data":"ysiEAwABAAAAAQAAD3huLS1hbnRoci12cmE3agdleGFtcGxlAAABAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeIW+mQAABwgAAAOEAAk6gAABUYA=","querytime":3,"answerfrom":"2001:dc3::35"}}},"Su5WLHq4snuuB/mBxXyF0Q":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935542.94235,"data":"mhKEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAYAAQAABgABAAAAAABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","querytime":3,"answerfrom":"2001:dc3::35"}}},"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":3,"answerfrom":"2001:dc3::35","data":"k96EAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.66816}}}}
|
||||
m.root-servers.net 202.12.27.33 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":3,"answerfrom":"202.12.27.33","timestamp":1646935543.65873,"data":"yH6EAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
k.root-servers.net 2001:07fd:0000:0000:0000:0000:0000:0001 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":8,"answerfrom":"2001:7fd::1","data":"oQmEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.59331}}}}
|
||||
k.root-servers.net 193.0.14.129 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":12,"answerfrom":"193.0.14.129","data":"pCmEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.56745}}}}
|
||||
g.root-servers.net 2001:0500:0012:0000:0000:0000:0000:0d0d {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":55,"answerfrom":"2001:500:12::d0d","data":"NNGEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.35495}}}}
|
||||
g.root-servers.net 192.112.36.4 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.26682,"data":"6cSEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","querytime":75,"answerfrom":"192.112.36.4"}}}}
|
||||
a.root-servers.net 198.41.0.4 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935542.98134,"data":"ZYGEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","answerfrom":"198.41.0.4","querytime":10}}}}
|
||||
a.root-servers.net 2001:0503:ba3e:0000:0000:0000:0002:0030 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"16WEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.00459,"querytime":10,"answerfrom":"2001:503:ba3e::2:30"}}}}
|
||||
f.root-servers.net 2001:0500:002f:0000:0000:0000:0000:000f {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"P+2EAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.25261,"answerfrom":"2001:500:2f::f","querytime":4}}}}
|
||||
f.root-servers.net 192.5.5.241 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1646935543.23842,"data":"VuuEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","querytime":3,"answerfrom":"192.5.5.241"}}}}
|
||||
h.root-servers.net 2001:0500:0001:0000:0000:0000:0000:0053 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"2001:500:1::53","querytime":9,"timestamp":1646935543.46742,"data":"NyuEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
h.root-servers.net 198.97.190.53 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"zsqEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.4237,"answerfrom":"198.97.190.53","querytime":31}}}}
|
||||
e.root-servers.net 192.203.230.10 {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":3,"answerfrom":"192.203.230.10","timestamp":1646935543.20502,"data":"C8CEAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA=="}}}}
|
||||
e.root-servers.net 2001:0500:00a8:0000:0000:0000:0000:000e {"OfMmGFE/IOgA39Hya8P8tQ":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"O0+EAwABAAAAAQAAC3huLS1jYWYtZG1hB2V4YW1wbGUAAAIAAQAABgABAAFRgABAAWEMcm9vdC1zZXJ2ZXJzA25ldAAFbnN0bGQMdmVyaXNpZ24tZ3JzA2NvbQB4hb6ZAAAHCAAAA4QACTqAAAFRgA==","timestamp":1646935543.21714,"querytime":9,"answerfrom":"2001:500:a8::e"}}}}
|
||||
126
zonemaster-backend/t/idn.t
Normal file
126
zonemaster-backend/t/idn.t
Normal file
@@ -0,0 +1,126 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Temp qw[tempdir];
|
||||
use Test::Exception;
|
||||
use Test::More; # see done_testing()
|
||||
use utf8;
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil qw( TestAgent );
|
||||
|
||||
use Zonemaster::Backend::Config;
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
|
||||
my $datafile = "$t_path/idn.data";
|
||||
TestUtil::restore_datafile( $datafile );
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
|
||||
my $configuration = <<"EOF";
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = en_US
|
||||
EOF
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
$configuration .= <<"EOF";
|
||||
[PUBLIC PROFILES]
|
||||
test_profile=$t_path/test_profile_network_true.json
|
||||
default=$t_path/test_profile_network_true.json
|
||||
EOF
|
||||
} else {
|
||||
$configuration .= <<"EOF";
|
||||
[PUBLIC PROFILES]
|
||||
test_profile=$t_path/test_profile_no_network.json
|
||||
default=$t_path/test_profile_no_network.json
|
||||
EOF
|
||||
}
|
||||
|
||||
my $config = Zonemaster::Backend::Config->parse( $configuration );
|
||||
|
||||
my $db = TestUtil::init_db( $config );
|
||||
my $agent = TestUtil::create_testagent( $config );
|
||||
|
||||
# define the default properties for the tests
|
||||
my $params = {
|
||||
client_id => 'Unit Test',
|
||||
client_version => '1.0',
|
||||
domain => 'café.example',
|
||||
ipv4 => JSON::PP::true,
|
||||
ipv6 => JSON::PP::true,
|
||||
profile => 'default',
|
||||
};
|
||||
|
||||
my $test_id;
|
||||
|
||||
subtest 'test IDN domain' => sub {
|
||||
$test_id = $db->create_new_test( $params->{domain}, $params, 10 );
|
||||
|
||||
my $res = $db->get_test_params( $test_id );
|
||||
note Dumper($res);
|
||||
is( $res->{domain}, $params->{domain}, 'Retrieve the correct "domain" value' );
|
||||
};
|
||||
|
||||
# run the test
|
||||
$db->claim_test( $test_id )
|
||||
or BAIL_OUT( "test needs to be claimed before calling run()" );
|
||||
$agent->run( $test_id ); # blocking call
|
||||
|
||||
subtest 'test get_test_results' => sub {
|
||||
my $res = $db->test_results( $test_id );
|
||||
is( $res->{params}->{domain}, $params->{domain}, 'Retrieve the correct domain name' );
|
||||
};
|
||||
|
||||
|
||||
subtest 'test IDN nameserver' => sub {
|
||||
$params->{nameservers} = [ { ns => "anøthær.example" } ];
|
||||
|
||||
$test_id = $db->create_new_test( $params->{domain}, $params, 10 );
|
||||
|
||||
subtest 'get_test_params' => sub {
|
||||
my $res = $db->get_test_params( $test_id );
|
||||
note Dumper($res);
|
||||
is_deeply( $res->{nameservers}, $params->{nameservers}, 'Retrieve the correct "nameservers" value' );
|
||||
};
|
||||
|
||||
# run the test
|
||||
$db->claim_test( $test_id )
|
||||
or BAIL_OUT( "test needs to be claimed before calling run()" );
|
||||
$agent->run( $test_id ); # blocking call
|
||||
|
||||
subtest 'test_results' => sub {
|
||||
my $res = $db->test_results( $test_id );
|
||||
is_deeply( $res->{params}->{nameservers}, $params->{nameservers}, 'Retrieve the correct nameservers parameters' );
|
||||
};
|
||||
};
|
||||
|
||||
TestUtil::save_datafile( $datafile );
|
||||
|
||||
done_testing();
|
||||
301
zonemaster-backend/t/lifecycle.t
Normal file
301
zonemaster-backend/t/lifecycle.t
Normal file
@@ -0,0 +1,301 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::Exception;
|
||||
use Test::NoWarnings;
|
||||
use Log::Any::Test;
|
||||
use Log::Any qw( $log );
|
||||
|
||||
my $TIME;
|
||||
BEGIN {
|
||||
$TIME = CORE::time();
|
||||
|
||||
*CORE::GLOBAL::time = sub { $TIME };
|
||||
}
|
||||
|
||||
use Data::Dumper;
|
||||
use File::ShareDir qw[dist_file];
|
||||
use File::Temp qw[tempdir];
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil;
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Backend::Config;
|
||||
use Zonemaster::Backend::DB qw( $TEST_WAITING $TEST_RUNNING $TEST_COMPLETED );
|
||||
|
||||
sub advance_time {
|
||||
my ( $delta ) = @_;
|
||||
$TIME += $delta;
|
||||
}
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[ZONEMASTER]
|
||||
age_reuse_previous_test = 10
|
||||
EOF
|
||||
|
||||
sub count_cancellation_messages {
|
||||
my $results = shift;
|
||||
return scalar grep { $_->{tag} eq 'UNABLE_TO_FINISH_TEST' } @{ $results->{results} };
|
||||
}
|
||||
|
||||
sub count_died_messages {
|
||||
my $results = shift;
|
||||
return scalar grep { $_->{tag} eq 'TEST_DIED' } @{ $results->{results} };
|
||||
}
|
||||
|
||||
subtest 'Everything but Test::NoWarnings' => sub {
|
||||
lives_ok { # Make sure we get to print log messages in case of errors.
|
||||
my $db = TestUtil::init_db( $config );
|
||||
|
||||
subtest 'State transitions' => sub {
|
||||
my $testid1 = $db->create_new_test( "1.transition.test", {}, 10 );
|
||||
is ref $testid1, '', "create_new_test should return 'testid' scalar";
|
||||
my $current_state = $db->test_state( $testid1 );
|
||||
is $current_state, $TEST_WAITING, "New test starts out in 'waiting' state.";
|
||||
|
||||
my @cases = (
|
||||
{
|
||||
old_state => $TEST_WAITING,
|
||||
transition => [ 'store_results', '{}' ],
|
||||
throws => qr/illegal transition/,
|
||||
},
|
||||
{
|
||||
old_state => $TEST_WAITING,
|
||||
transition => ['claim_test'],
|
||||
returns => 1, # true
|
||||
new_state => $TEST_RUNNING,
|
||||
},
|
||||
{
|
||||
old_state => $TEST_RUNNING,
|
||||
transition => ['claim_test'],
|
||||
returns => '', # false
|
||||
},
|
||||
{
|
||||
old_state => $TEST_RUNNING,
|
||||
transition => [ 'store_results', '{}' ],
|
||||
returns => undef,
|
||||
new_state => $TEST_COMPLETED,
|
||||
},
|
||||
{
|
||||
old_state => $TEST_COMPLETED,
|
||||
transition => ['claim_test'],
|
||||
returns => '', #false
|
||||
},
|
||||
{
|
||||
old_state => $TEST_COMPLETED,
|
||||
transition => [ 'store_results', '{}' ],
|
||||
throws => qr/illegal transition/,
|
||||
},
|
||||
);
|
||||
|
||||
for my $case ( @cases ) {
|
||||
if ( $case->{old_state} ne $current_state ) {
|
||||
BAIL_OUT( "Assuming to be in '$case->{old_state}' but we're actually in '$current_state'!" );
|
||||
}
|
||||
|
||||
my ( $transition, @args ) = @{ $case->{transition} };
|
||||
|
||||
if ( exists $case->{returns} ) {
|
||||
my $rv_string = Data::Dumper->new( [ $case->{returns} ] )->Indent( 0 )->Terse( 1 )->Dump;
|
||||
|
||||
my $result = $db->$transition( $testid1, @args );
|
||||
is $result,
|
||||
$case->{returns},
|
||||
"In state '$case->{old_state}' transition '$transition' should return $rv_string,";
|
||||
|
||||
if ( $case->{new_state} ) {
|
||||
$current_state = $db->test_state( $testid1 );
|
||||
is $current_state,
|
||||
$case->{new_state},
|
||||
"and it should move the test to '$case->{new_state}' state.";
|
||||
}
|
||||
else {
|
||||
$current_state = $db->test_state( $testid1 );
|
||||
is $current_state,
|
||||
$case->{old_state},
|
||||
"and it should not affect the actual state.";
|
||||
}
|
||||
}
|
||||
elsif ( exists $case->{throws} ) {
|
||||
throws_ok {
|
||||
$db->$transition( $testid1, @args )
|
||||
}
|
||||
$case->{throws}, "In state '$case->{old_state}' transition '$transition' should throw an exception,";
|
||||
|
||||
$current_state = $db->test_state( $testid1 );
|
||||
is $current_state,
|
||||
$case->{old_state},
|
||||
"and it should not affect the actual state.";
|
||||
}
|
||||
else {
|
||||
BAIL_OUT( "Invalid case specification!" );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'Progress' => sub {
|
||||
my $testid1 = $db->create_new_test( "1.progress.test", {}, 10 );
|
||||
is ref $testid1, '', "create_new_test should return 'testid' scalar";
|
||||
|
||||
throws_ok { $db->test_progress( $testid1, 1 ) } qr/illegal update/, "Setting progress should throw an exception in 'waiting' state.";
|
||||
|
||||
$db->claim_test( $testid1 );
|
||||
|
||||
# Logically progress is 0 entering the 'running' state, but because
|
||||
# of implementation details we're clamping it to the range 1-99
|
||||
# inclusive.
|
||||
is $db->test_progress( $testid1 ), 1, "Progress should be 1 entering the 'running' state.";
|
||||
|
||||
is $db->test_progress( $testid1, 0 ), 1, "Setting progress to 0 should succeed, but actual clamped value is returned,";
|
||||
is $db->test_progress( $testid1 ), 1, "and it should persist at the clamped value.";
|
||||
is $db->test_progress( $testid1, 0 ), 1, "Setting the same progress again should succeed.";
|
||||
|
||||
is $db->test_progress( $testid1, 2 ), 2, "Setting a higher progress should be allowed,";
|
||||
is $db->test_progress( $testid1 ), 2, "and it should persist at the new value.";
|
||||
is $db->test_progress( $testid1, 2 ), 2, "Setting the same progress again should succeed.";
|
||||
|
||||
throws_ok { $db->test_progress( $testid1, 0 ) } qr/illegal update/, "Setting a lower progress should throw an exception,";
|
||||
is $db->test_progress( $testid1 ), 2, "and it should persist at the old value.";
|
||||
|
||||
is $db->test_progress( $testid1, 100 ), 99, "Setting progress to 100 should succeed, but actual clamped value is returned,";
|
||||
is $db->test_progress( $testid1 ), 99, "and it should persist at the clamped value.";
|
||||
|
||||
$db->store_results( $testid1, '{}' );
|
||||
|
||||
throws_ok { $db->test_progress( $testid1, 100 ) } qr/illegal update/, "Setting progress should throw an exception in 'completed' state.";
|
||||
};
|
||||
|
||||
subtest 'Testid reuse' => sub {
|
||||
my $testid1 = $db->create_new_test( "zone1.rpcapi.example", {}, 10 );
|
||||
is ref $testid1, '', 'create_new_test returns "testid" scalar';
|
||||
|
||||
advance_time( 11 );
|
||||
my $testid2 = $db->create_new_test( "zone1.rpcapi.example", {}, 10 );
|
||||
is $testid2, $testid1, 'reuse is determined from start time (as opposed to creation time)';
|
||||
|
||||
$db->claim_test( $testid1 );
|
||||
advance_time( 10 );
|
||||
|
||||
my $testid3 = $db->create_new_test( "zone1.rpcapi.example", {}, 10 );
|
||||
is $testid3, $testid1, 'old testid is reused before it expires';
|
||||
|
||||
advance_time( 1 );
|
||||
my $testid4 = $db->create_new_test( "zone1.rpcapi.example", {}, 10 );
|
||||
isnt $testid4, $testid1, 'a new testid is generated after the old one expires';
|
||||
};
|
||||
|
||||
subtest 'Termination of timed out tests' => sub {
|
||||
my $testid2 = $db->create_new_test( "zone2.rpcapi.example", {}, 10 );
|
||||
my $testid3 = $db->create_new_test( "zone3.rpcapi.example", {}, 10 );
|
||||
|
||||
# testid2 started 11 seconds ago, testid3 started 10 seconds ago
|
||||
$db->claim_test( $testid2 );
|
||||
advance_time( 1 );
|
||||
$db->claim_test( $testid3 );
|
||||
advance_time( 10 );
|
||||
|
||||
$db->process_unfinished_tests( undef, 10 );
|
||||
|
||||
is $db->test_progress( $testid3 ), 1, 'leave test alone AT its timeout';
|
||||
is $db->test_progress( $testid2 ), 100, 'terminate test AFTER its timeout';
|
||||
|
||||
is count_cancellation_messages( $db->test_results( $testid3 ) ), 0, 'no cancellation message present AT timeout';
|
||||
is count_cancellation_messages( $db->test_results( $testid2 ) ), 1, 'one cancellation message present AFTER timeout';
|
||||
};
|
||||
|
||||
subtest 'Termination of crashed tests' => sub {
|
||||
my $testid4 = $db->create_new_test( "zone4.rpcapi.example", {}, 10 );
|
||||
$db->claim_test( $testid4 );
|
||||
|
||||
$db->process_dead_test( $testid4 );
|
||||
|
||||
is $db->test_progress( $testid4 ), 100, 'terminates test';
|
||||
|
||||
is count_died_messages( $db->test_results( $testid4 ) ), 1, 'one died message present after crash';
|
||||
};
|
||||
|
||||
subtest 'Do not reuse batch tests' => sub {
|
||||
my %user = (
|
||||
username => "user",
|
||||
api_key => "key"
|
||||
);
|
||||
my @domains = ( 'zone1.rpcapi.example', 'zone5.rpcapi.example' );
|
||||
my $params = {
|
||||
%user,
|
||||
domains => \@domains,
|
||||
test_params => {
|
||||
priority => 5,
|
||||
queue => 0
|
||||
}
|
||||
};
|
||||
$db->add_api_user( $user{username}, $user{api_key} );
|
||||
my $batch_id = $db->add_batch_job( $params );
|
||||
|
||||
my @batch_test_ids = $db->dbh->selectall_array(
|
||||
q[
|
||||
SELECT hash_id
|
||||
FROM test_results
|
||||
WHERE batch_id = ?
|
||||
],
|
||||
undef,
|
||||
$batch_id
|
||||
);
|
||||
@batch_test_ids = map { $$_[0] } @batch_test_ids;
|
||||
|
||||
if ( @batch_test_ids != 2 ) {
|
||||
BAIL_OUT( 'There should be 2 tests in database for this batch_id' );
|
||||
}
|
||||
|
||||
my ( $count_zone1 ) = $db->dbh->selectrow_array(
|
||||
q[
|
||||
SELECT count(*)
|
||||
FROM test_results
|
||||
WHERE domain = 'zone1.rpcapi.example'
|
||||
]
|
||||
);
|
||||
is( $count_zone1, 3, '3 tests for domain "zone1.rpcapi.example' );
|
||||
my $test_id = $db->create_new_test( 'zone5.rpcapi.example', {}, 10 );
|
||||
ok( ! grep(/$test_id/, @batch_test_ids), 'new single test should not reuse batch tests' );
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
for my $msg ( @{ $log->msgs } ) {
|
||||
my $text = sprintf( "%s: %s", $msg->{level}, $msg->{message} );
|
||||
if ( $msg->{level} =~ /trace|debug|info|notice/ ) {
|
||||
note $text;
|
||||
}
|
||||
else {
|
||||
diag $text;
|
||||
}
|
||||
}
|
||||
37
zonemaster-backend/t/manifest.t
Normal file
37
zonemaster-backend/t/manifest.t
Normal file
@@ -0,0 +1,37 @@
|
||||
#!perl
|
||||
use v5.14.2;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
|
||||
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
|
||||
|
||||
my $makebin = 'make';
|
||||
|
||||
sub make {
|
||||
my @make_args = @_;
|
||||
|
||||
undef $ENV{MAKEFLAGS};
|
||||
|
||||
my $command = join( ' ', $makebin, '-s', @make_args );
|
||||
my $output = `$command 2>&1`;
|
||||
|
||||
if ( $? == -1 ) {
|
||||
BAIL_OUT( "failed to execute: $!" );
|
||||
}
|
||||
elsif ( $? & 127 ) {
|
||||
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
|
||||
}
|
||||
|
||||
return $output, $? >> 8;
|
||||
}
|
||||
|
||||
subtest "distcheck" => sub {
|
||||
my ( $output, $status ) = make "distcheck";
|
||||
is $status, 0, $makebin . ' distcheck exits with value 0';
|
||||
is $output, "", $makebin . ' distcheck gives empty output';
|
||||
};
|
||||
237
zonemaster-backend/t/parameters_validation.t
Normal file
237
zonemaster-backend/t/parameters_validation.t
Normal file
@@ -0,0 +1,237 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 4;
|
||||
use Test::NoWarnings;
|
||||
|
||||
use Cwd;
|
||||
use File::Temp qw[tempdir];
|
||||
use Zonemaster::Backend::Config;
|
||||
use Zonemaster::Backend::RPCAPI;
|
||||
use JSON::Validator::Joi "joi";
|
||||
use JSON::PP;
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $cwd = cwd();
|
||||
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
test = $cwd/t/test_profile.json
|
||||
EOF
|
||||
|
||||
my $rpcapi = Zonemaster::Backend::RPCAPI->new(
|
||||
{
|
||||
dbtype => $config->DB_engine,
|
||||
config => $config,
|
||||
}
|
||||
);
|
||||
|
||||
sub test_validation {
|
||||
my ( $method_name, $method_schema, $test_cases ) = @_;
|
||||
|
||||
subtest "Method $method_name" => sub {
|
||||
for my $test_case (@$test_cases) {
|
||||
subtest 'Test case: ' . $test_case->{name} => sub {
|
||||
my @res = $rpcapi->validate_params( $method_schema, $test_case->{input});
|
||||
is_deeply(\@res, $test_case->{output}, 'Matched validation output' ) or diag( encode_json \@res);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
subtest 'Test JSON schema' => sub {
|
||||
my $test_joi_schema = joi->new->object->strict->props(
|
||||
hostname => joi->new->string->max(10)->required
|
||||
);
|
||||
|
||||
my $test_raw_schema = {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
required => [ 'hostname' ],
|
||||
properties => {
|
||||
hostname => {
|
||||
type => 'string',
|
||||
maxLength => 10
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
my $test_cases = [
|
||||
{
|
||||
name => 'Empty request',
|
||||
input => {},
|
||||
output => [{
|
||||
message => 'Missing property',
|
||||
path => '/hostname'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name => 'Correct request',
|
||||
input => {
|
||||
hostname => 'example'
|
||||
},
|
||||
output => []
|
||||
},
|
||||
{
|
||||
name => 'Bad request',
|
||||
input => {
|
||||
hostname => 'example.toolong'
|
||||
},
|
||||
output => [{
|
||||
message => 'String is too long: 15/10.',
|
||||
path => '/hostname'
|
||||
}]
|
||||
}
|
||||
];
|
||||
|
||||
test_validation 'test_joi', $test_joi_schema, $test_cases;
|
||||
test_validation 'test_raw', $test_raw_schema, $test_cases;
|
||||
};
|
||||
|
||||
subtest 'Test custom error message' => sub {
|
||||
my $test_custom_error_schema = {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
required => [ 'hostname' ],
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
hostname => {
|
||||
type => 'string',
|
||||
'x-error-message' => 'Bad hostname, should be a string less than 10 characters long',
|
||||
maxLength => 10
|
||||
},
|
||||
nameservers => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
required => [ 'ip' ],
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
ip => {
|
||||
type => 'string',
|
||||
'x-error-message' => 'Bad IP address',
|
||||
pattern => '^[a-f0-9\.:]+$'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
my $test_cases = [
|
||||
{
|
||||
name => 'Bad input',
|
||||
input => {
|
||||
hostname => 'This is a bad input',
|
||||
nameservers => [
|
||||
{ ip => 'Very bad indeed'},
|
||||
{ ip => '10.10.10.10' },
|
||||
{ ip => 'But not the previous property' }
|
||||
]
|
||||
},
|
||||
output => [
|
||||
{
|
||||
path => '/hostname',
|
||||
message => 'Bad hostname, should be a string less than 10 characters long',
|
||||
},
|
||||
{
|
||||
path => '/nameservers/0/ip',
|
||||
message => 'Bad IP address',
|
||||
},
|
||||
{
|
||||
path => '/nameservers/2/ip',
|
||||
message => 'Bad IP address',
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
test_validation 'test_custom_error', $test_custom_error_schema, $test_cases;
|
||||
};
|
||||
|
||||
subtest 'Test custom formats' => sub {
|
||||
my $test_extra_validator_schema = {
|
||||
type => 'object',
|
||||
properties => {
|
||||
my_ip => {
|
||||
type => 'string',
|
||||
format => 'ip',
|
||||
},
|
||||
my_lang => {
|
||||
type => 'string',
|
||||
format => 'language_tag',
|
||||
},
|
||||
my_domain => {
|
||||
type => 'string',
|
||||
format => 'domain',
|
||||
},
|
||||
my_profile => {
|
||||
type => 'string',
|
||||
format => 'profile',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
my $test_cases = [
|
||||
{
|
||||
name => 'Input ok',
|
||||
input => {
|
||||
my_ip => '192.0.2.1',
|
||||
my_lang => 'en',
|
||||
my_domain => 'zonemaster.net',
|
||||
my_profile => 'test',
|
||||
},
|
||||
output => []
|
||||
},
|
||||
{
|
||||
name => 'Bad ip',
|
||||
input => {
|
||||
my_ip => 'abc',
|
||||
},
|
||||
output => [{
|
||||
path => '/my_ip',
|
||||
message => 'Invalid IP address'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name => 'Bad language format',
|
||||
input => {
|
||||
my_lang => 'abc',
|
||||
},
|
||||
output => [{
|
||||
path => '/my_lang',
|
||||
message => 'Invalid language tag format'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name => 'Bad domain',
|
||||
input => {
|
||||
my_domain => 'not a domain',
|
||||
},
|
||||
output => [{
|
||||
path => '/my_domain',
|
||||
message => 'Domain name has an ASCII label ("not a domain") with a character not permitted.'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name => 'Bad profile',
|
||||
input => {
|
||||
my_profile => 'other_profile',
|
||||
},
|
||||
output => [{
|
||||
path => '/my_profile',
|
||||
message => 'Unknown profile'
|
||||
}]
|
||||
},
|
||||
];
|
||||
|
||||
test_validation 'test_extra_validator', $test_extra_validator_schema, $test_cases;
|
||||
};
|
||||
66
zonemaster-backend/t/po-files.t
Normal file
66
zonemaster-backend/t/po-files.t
Normal file
@@ -0,0 +1,66 @@
|
||||
#!perl
|
||||
|
||||
# This file is not included in the distribution package and not run
|
||||
# at installation with cpanm().
|
||||
|
||||
use v5.14.2;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More; # see done_testing()
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
|
||||
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
|
||||
chdir 'share' or BAIL_OUT( "chdir: $!" );
|
||||
|
||||
my $makebin = 'make';
|
||||
|
||||
sub make {
|
||||
my @make_args = @_;
|
||||
|
||||
undef $ENV{MAKEFLAGS};
|
||||
|
||||
my $command = join( ' ', $makebin, '--silent', '--no-print-directory', @make_args );
|
||||
my $output = `$command`;
|
||||
|
||||
if ( $? == -1 ) {
|
||||
BAIL_OUT( "failed to execute: $!" );
|
||||
}
|
||||
elsif ( $? & 127 ) {
|
||||
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
|
||||
}
|
||||
|
||||
return $output, $? >> 8;
|
||||
}
|
||||
|
||||
subtest "no fuzzy marks" => sub {
|
||||
my ( $output, $status ) = make "show-fuzzy";
|
||||
is $status, 0, $makebin . ' show-fuzzy exits with value 0';
|
||||
is $output, "", $makebin . ' show-fuzzy gives empty output';
|
||||
};
|
||||
|
||||
subtest "check po files" => sub {
|
||||
my ( $output, $status ) = make "check-po";
|
||||
is $status, 0, $makebin . ' check-po exits with value 0';
|
||||
is $output, "", $makebin . ' check-po gives empty output';
|
||||
};
|
||||
|
||||
subtest "tidy po files" => sub {
|
||||
SKIP: {
|
||||
my ( $output, $status );
|
||||
|
||||
$output = `git diff --numstat`;
|
||||
|
||||
skip 'git repo should be clean to run this test', 3 if $output ne '';
|
||||
|
||||
( $output, $status ) = make "tidy-po";
|
||||
is $status, 0, $makebin . ' tidy-po exits with value 0';
|
||||
is $output, "", $makebin . ' tidy-po gives empty output';
|
||||
|
||||
$output = `git diff --numstat`;
|
||||
is $output, "", 'all files are tidied (if not run "make tidy-po")';
|
||||
}
|
||||
};
|
||||
|
||||
done_testing();
|
||||
86
zonemaster-backend/t/queue.t
Normal file
86
zonemaster-backend/t/queue.t
Normal file
@@ -0,0 +1,86 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
use Log::Any::Test;
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Temp qw( tempdir );
|
||||
use Log::Any qw( $log );
|
||||
use Test::Differences;
|
||||
use Test::Exception;
|
||||
use Zonemaster::Backend::Config;
|
||||
use Zonemaster::Backend::DB qw( $TEST_RUNNING );
|
||||
use Zonemaster::Engine;
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil;
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[ZONEMASTER]
|
||||
age_reuse_previous_test = 10
|
||||
EOF
|
||||
|
||||
subtest 'Everything but Test::NoWarnings' => sub {
|
||||
lives_ok { # Make sure we get to print log messages in case of errors.
|
||||
my $db = TestUtil::init_db( $config );
|
||||
|
||||
subtest 'Claiming waiting tests for processing' => sub {
|
||||
eq_or_diff
|
||||
[ $db->get_test_request( undef ) ],
|
||||
[ undef, undef ],
|
||||
"An empty list is returned when queue is empty";
|
||||
|
||||
my $testid1 = $db->create_new_test( "1.claim.test", {}, 10 );
|
||||
eq_or_diff
|
||||
[ $db->get_test_request( undef ) ],
|
||||
[ $testid1, undef ],
|
||||
"A waiting test is returned if one is available";
|
||||
eq_or_diff
|
||||
[ $db->get_test_request( undef ) ],
|
||||
[ undef, undef ],
|
||||
"Claimed test is removed from queue";
|
||||
is
|
||||
$db->test_state( $testid1 ),
|
||||
$TEST_RUNNING,
|
||||
"Claimed test is in 'running' state";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
for my $msg ( @{ $log->msgs } ) {
|
||||
my $text = sprintf( "%s: %s", $msg->{level}, $msg->{message} );
|
||||
if ( $msg->{level} =~ /trace|debug|info|notice/ ) {
|
||||
note $text;
|
||||
}
|
||||
else {
|
||||
diag $text;
|
||||
}
|
||||
}
|
||||
242
zonemaster-backend/t/rpc_validation.t
Normal file
242
zonemaster-backend/t/rpc_validation.t
Normal file
@@ -0,0 +1,242 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 30;
|
||||
use Test::NoWarnings;
|
||||
|
||||
use Cwd;
|
||||
use File::Temp qw[tempdir];
|
||||
use Zonemaster::Backend::Config;
|
||||
use Zonemaster::Backend::RPCAPI;
|
||||
use JSON::Validator::Joi "joi";
|
||||
use JSON::PP;
|
||||
|
||||
###
|
||||
### Setup
|
||||
###
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $cwd = cwd();
|
||||
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[PUBLIC PROFILES]
|
||||
test = $cwd/t/test_profile.json
|
||||
EOF
|
||||
|
||||
my $rpcapi = Zonemaster::Backend::RPCAPI->new(
|
||||
{
|
||||
dbtype => $config->DB_engine,
|
||||
config => $config,
|
||||
}
|
||||
);
|
||||
|
||||
###
|
||||
### JSONRPC request object construction helper
|
||||
###
|
||||
|
||||
sub jsonrpc
|
||||
{
|
||||
my ($method, $params, $force_undef) = @_;
|
||||
my $object = {
|
||||
jsonrpc => '2.0',
|
||||
id => 'testing',
|
||||
method => $method
|
||||
};
|
||||
if (defined $params or $force_undef) {
|
||||
$object->{params} = $params;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
###
|
||||
### JSONRPC error response construction helpers
|
||||
###
|
||||
|
||||
sub jsonrpc_error
|
||||
{
|
||||
my ($message, $code, $data, $id) = @_;
|
||||
my $object = {
|
||||
jsonrpc => '2.0',
|
||||
id => $id,
|
||||
error => {
|
||||
message => $message,
|
||||
code => $code
|
||||
}
|
||||
};
|
||||
$object->{error}{data} = $data if defined $data;
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub error_bad_jsonrpc
|
||||
{
|
||||
my ($data) = @_;
|
||||
|
||||
jsonrpc_error('The JSON sent is not a valid request object.', '-32600', $data, undef);
|
||||
}
|
||||
|
||||
sub error_missing_params
|
||||
{
|
||||
jsonrpc_error("Missing 'params' object", '-32602', undef, 'testing');
|
||||
}
|
||||
|
||||
sub error_bad_params
|
||||
{
|
||||
my ($messages) = @_;
|
||||
|
||||
my @data;
|
||||
|
||||
while (@$messages) {
|
||||
my $path = shift @$messages;
|
||||
my $message = shift @$messages;
|
||||
push @data, { path => $path, message => $message };
|
||||
}
|
||||
|
||||
jsonrpc_error('Invalid method parameter(s).', '-32602', \@data, 'testing');
|
||||
}
|
||||
|
||||
sub no_error
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
###
|
||||
### Test wrapper functions
|
||||
###
|
||||
|
||||
sub test_validation
|
||||
{
|
||||
my ($input, $output, $message) = @_;
|
||||
|
||||
my $res = $rpcapi->jsonrpc_validate($input);
|
||||
is_deeply($res, $output, $message) or diag(encode_json($res));
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
### The tests themselves
|
||||
###
|
||||
|
||||
test_validation undef,
|
||||
error_bad_jsonrpc('/: Expected object - got null.'),
|
||||
"Sending undef is an error";
|
||||
|
||||
test_validation JSON::PP::false,
|
||||
error_bad_jsonrpc('/: Expected object - got boolean.'),
|
||||
"Sending a boolean is an error";
|
||||
|
||||
test_validation -1,
|
||||
error_bad_jsonrpc('/: Expected object - got number.'),
|
||||
"Sending a number is an error";
|
||||
|
||||
test_validation "hello",
|
||||
error_bad_jsonrpc('/: Expected object - got string.'),
|
||||
"Sending a string is an error";
|
||||
|
||||
test_validation [qw(a b c)],
|
||||
error_bad_jsonrpc('/: Expected object - got array.'),
|
||||
"Sending an array is an error";
|
||||
|
||||
test_validation {},
|
||||
error_bad_jsonrpc('/jsonrpc: Missing property. /method: Missing property.'),
|
||||
"Sending an empty object is an error";
|
||||
|
||||
test_validation { jsonrpc => '2.0' },
|
||||
error_bad_jsonrpc('/method: Missing property.'),
|
||||
"Sending an incomplete object is an error";
|
||||
|
||||
test_validation { jsonrpc => '2.0', method => 'system_versions' },
|
||||
error_bad_jsonrpc(''),
|
||||
"Sending an object with no ID is an error";
|
||||
|
||||
test_validation { jsonrpc => '2.0', method => 'system_versions', id => JSON::PP::false },
|
||||
error_bad_jsonrpc('/id: Expected null/number/string - got boolean.'),
|
||||
"Sending an object whose ID is a boolean is an error";
|
||||
|
||||
test_validation { jsonrpc => '2.0', method => 'system_versions', id => [qw(a b c)] },
|
||||
error_bad_jsonrpc('/id: Expected null/number/string - got array.'),
|
||||
"Sending an object whose ID is an array is an error";
|
||||
|
||||
test_validation { jsonrpc => '2.0', method => 'system_versions', id => { a => 1 } },
|
||||
error_bad_jsonrpc('/id: Expected null/number/string - got object.'),
|
||||
"Sending an object whose ID is an object is an error";
|
||||
|
||||
test_validation jsonrpc("job_status"),
|
||||
error_missing_params(),
|
||||
"Calling job_status without parameters is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", undef, 1),
|
||||
error_bad_params(["/" => "Expected object - got null."]),
|
||||
"Passing null as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", JSON::PP::false),
|
||||
error_bad_params(["/" => "Expected object - got boolean."]),
|
||||
"Passing boolean as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", 1),
|
||||
error_bad_params(["/" => "Expected object - got number."]),
|
||||
"Passing number as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", "hello"),
|
||||
error_bad_params(["/" => "Expected object - got string."]),
|
||||
"Passing string as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", [qw(a b c)]),
|
||||
error_bad_params(["/" => "Expected object - got array."]),
|
||||
"Passing array as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", {}),
|
||||
error_bad_params(["/job_id" => "Missing property"]),
|
||||
"Passing empty object as parameter to job_status is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", { job_id => 'this_will_definitely_never_ever_exist' }),
|
||||
error_bad_params(["/job_id" => 'String does not match (?^u:^[0-9a-f]{16}$).']),
|
||||
"Calling job_status with a bad job_id is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", { job_id => '0123456789abcdef', data => "something" }),
|
||||
error_bad_params(["/" => "Properties not allowed: data."]),
|
||||
"Calling job_status with unknown parameters is an error";
|
||||
|
||||
test_validation jsonrpc("job_status", { job_id => '0123456789abcdef' }),
|
||||
no_error,
|
||||
"Calling job_status with a good job_id succeeds";
|
||||
|
||||
test_validation jsonrpc("system_versions"),
|
||||
no_error,
|
||||
"Calling system_versions with no parameters is OK";
|
||||
|
||||
test_validation jsonrpc("system_versions", undef, 1),
|
||||
error_bad_params(["/" => "Expected object - got null."]),
|
||||
"Passing null as parameter to system_versions is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", JSON::PP::false),
|
||||
error_bad_params(["/" => "Expected object - got boolean."]),
|
||||
"Passing number as parameter to system_versions is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", -1),
|
||||
error_bad_params(["/" => "Expected object - got number."]),
|
||||
"Passing number as parameter to system_versions is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", "hello"),
|
||||
error_bad_params(["/" => "Expected object - got string."]),
|
||||
"Passing string as parameter to system_versions is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", [qw(a b c)]),
|
||||
error_bad_params(["/" => "Expected object - got array."]),
|
||||
"Passing array as parameter to system_versions is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", { data => "something" }),
|
||||
error_bad_params(["/" => "Properties not allowed: data."]),
|
||||
"Calling system_versions with unrecognized parameter is an error";
|
||||
|
||||
test_validation jsonrpc("system_versions", {}),
|
||||
no_error,
|
||||
"Calling system_versions with empty object succeeds";
|
||||
118
zonemaster-backend/t/test01.data
Normal file
118
zonemaster-backend/t/test01.data
Normal file
File diff suppressed because one or more lines are too long
425
zonemaster-backend/t/test01.t
Normal file
425
zonemaster-backend/t/test01.t
Normal file
@@ -0,0 +1,425 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
|
||||
my $t_path;
|
||||
BEGIN {
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use File::Basename qw( dirname );
|
||||
$t_path = dirname( rel2abs( $0 ) );
|
||||
}
|
||||
use lib $t_path;
|
||||
use TestUtil qw( RPCAPI TestAgent );
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Temp qw[tempdir];
|
||||
use Test::Exception;
|
||||
use Test::More; # see done_testing()
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Backend::Config;
|
||||
|
||||
my $db_backend = TestUtil::db_backend();
|
||||
|
||||
my $datafile = "$t_path/test01.data";
|
||||
TestUtil::restore_datafile( $datafile );
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
|
||||
my $configuration = <<"EOF";
|
||||
[DB]
|
||||
engine = $db_backend
|
||||
|
||||
[MYSQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[POSTGRESQL]
|
||||
host = localhost
|
||||
user = zonemaster_test
|
||||
password = zonemaster
|
||||
database = zonemaster_test
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = en_US
|
||||
EOF
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
$configuration .= <<"EOF";
|
||||
[PUBLIC PROFILES]
|
||||
test_profile=$t_path/test_profile_network_true.json
|
||||
default=$t_path/test_profile_network_true.json
|
||||
EOF
|
||||
} else {
|
||||
$configuration .= <<"EOF";
|
||||
[PUBLIC PROFILES]
|
||||
test_profile=$t_path/test_profile_no_network.json
|
||||
default=$t_path/test_profile_no_network.json
|
||||
EOF
|
||||
}
|
||||
|
||||
my $config = Zonemaster::Backend::Config->parse( $configuration );
|
||||
|
||||
my $rpcapi = TestUtil::create_rpcapi( $config );
|
||||
|
||||
my $dbh = $rpcapi->{db}->dbh;
|
||||
|
||||
# Create the agent
|
||||
my $agent = TestUtil::create_testagent( $config );
|
||||
|
||||
# define the default properties for the tests
|
||||
my $params = {
|
||||
client_id => 'Unit Test',
|
||||
client_version => '1.0',
|
||||
domain => 'afnic.fr',
|
||||
ipv4 => JSON::PP::true,
|
||||
ipv6 => JSON::PP::true,
|
||||
profile => 'test_profile',
|
||||
|
||||
nameservers => [
|
||||
{ ns => 'ns1.nic.fr' },
|
||||
{ ns => 'ns2.nic.fr', ip => '192.134.4.1' }
|
||||
],
|
||||
ds_info => [
|
||||
{
|
||||
keytag => 11627,
|
||||
algorithm => 8,
|
||||
digtype => 2,
|
||||
digest => 'a6cca9e6027ecc80ba0f6d747923127f1d69005fe4f0ec0461bd633482595448'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
my $hash_id;
|
||||
|
||||
# This is the first test added to the DB, its 'id' is 1
|
||||
my $test_id = 1;
|
||||
subtest 'add a first test' => sub {
|
||||
$hash_id = $rpcapi->start_domain_test( $params );
|
||||
|
||||
ok( $hash_id, "API start_domain_test OK" );
|
||||
is( length($hash_id), 16, "Test has a 16 characters length hash ID (hash_id=$hash_id)" );
|
||||
|
||||
my ( $test_id_db, $hash_id_db ) = $dbh->selectrow_array( "SELECT id, hash_id FROM test_results WHERE id=?", undef, $test_id );
|
||||
is( $test_id_db, $test_id , 'API start_domain_test -> Test inserted in the DB' );
|
||||
is( $hash_id_db, $hash_id , 'Correct hash_id in database' );
|
||||
|
||||
# test test_progress API
|
||||
my $progress = $rpcapi->test_progress( { test_id => $hash_id } );
|
||||
is( $progress, 0 , 'Test has been created, its progress is 0' );
|
||||
};
|
||||
|
||||
subtest 'get and run test' => sub {
|
||||
my ( $hash_id_from_db ) = $rpcapi->{db}->get_test_request();
|
||||
is( $hash_id_from_db, $hash_id, 'Get correct test to run' );
|
||||
|
||||
my $progress = $rpcapi->test_progress( { test_id => $hash_id } );
|
||||
is( $progress, 1, 'Test has been picked, its progress is 1' );
|
||||
|
||||
diag "running the agent on test $hash_id";
|
||||
$agent->run( $hash_id ); # blocking call
|
||||
|
||||
$progress = $rpcapi->test_progress( { test_id => $hash_id } );
|
||||
is( $progress, 100 , 'Test has finished, its progress is 100' );
|
||||
};
|
||||
|
||||
subtest 'API calls' => sub {
|
||||
|
||||
subtest 'get_test_results' => sub {
|
||||
local $@ = undef;
|
||||
my $res = eval { $rpcapi->get_test_results( { id => $hash_id, language => 'en' } ) };
|
||||
if ( $@ ) {
|
||||
fail 'Crashed while fetching job results: ' . Dumper( $@ );
|
||||
}
|
||||
ok( ! defined $res->{id}, 'Do not expose primary key' );
|
||||
is( $res->{hash_id}, $hash_id, 'Retrieve the correct "hash_id"' );
|
||||
ok( defined $res->{params}, 'Value "params" properly defined' );
|
||||
ok( ! exists $res->{creation_time}, 'Key "creation_time" should be missing' );
|
||||
ok( defined $res->{created_at}, 'Value "created_at" properly defined' );
|
||||
ok( defined $res->{results}, 'Value "results" properly defined' );
|
||||
if ( @{ $res->{results} } > 1 ) {
|
||||
pass 'The test has some results';
|
||||
}
|
||||
else {
|
||||
fail 'The test has some results: ' . Dumper( $res->{results} );
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'get_test_params' => sub {
|
||||
my $res = $rpcapi->get_test_params( { test_id => $hash_id } );
|
||||
is( $res->{domain}, $params->{domain}, 'Retrieve the correct "domain" value' );
|
||||
is( $res->{profile}, $params->{profile}, 'Retrieve the correct "profile" value' );
|
||||
is( $res->{client_id}, $params->{client_id}, 'Retrieve the correct "client_id" value' );
|
||||
is( $res->{client_version}, $params->{client_version}, 'Retrieve the correct "client_version" value' );
|
||||
is( $res->{ipv4}, $params->{ipv4}, 'Retrieve the correct "ipv4" value' );
|
||||
is( $res->{ipv6}, $params->{ipv6}, 'Retrieve the correct "ipv6" value' );
|
||||
is_deeply( $res->{nameservers}, $params->{nameservers}, 'Retrieve the correct "nameservers" value' );
|
||||
is_deeply( $res->{ds_info}, $params->{ds_info}, 'Retrieve the correct "ds_info" value' );
|
||||
};
|
||||
|
||||
subtest 'add_api_user' => sub {
|
||||
my $res;
|
||||
eval {
|
||||
$res = $rpcapi->add_api_user( { username => "zonemaster_test", api_key => "zonemaster_test's api key" } );
|
||||
};
|
||||
is( $res, 1, 'API add_api_user success');
|
||||
|
||||
my $user_check_query = q/SELECT * FROM users WHERE username = 'zonemaster_test'/;
|
||||
is( scalar( $dbh->selectrow_array( $user_check_query ) ), 1 ,'API add_api_user user created' );
|
||||
};
|
||||
|
||||
subtest 'version_info' => sub {
|
||||
my $res = $rpcapi->version_info();
|
||||
ok( defined( $res->{zonemaster_ldns} ), 'Has a "zonemaster_ldns" key' );
|
||||
ok( defined( $res->{zonemaster_engine} ), 'Has a "zonemaster_engine" key' );
|
||||
ok( defined( $res->{zonemaster_backend} ), 'Has a "zonemaster_backend" key' );
|
||||
};
|
||||
|
||||
subtest 'profile_names' => sub {
|
||||
my $res = $rpcapi->profile_names();
|
||||
is( scalar( @$res ), 2, 'There are exactly 2 public profiles' );
|
||||
ok( grep( /default/, @$res ), 'The profile "default" is defined' );
|
||||
ok( grep( /test_profile/, @$res ), 'The profile "test_profile" is defined' );
|
||||
};
|
||||
|
||||
subtest 'get_data_from_parent_zone' => sub {
|
||||
my $res = $rpcapi->get_data_from_parent_zone( { domain => "fr" } );
|
||||
note explain( $res );
|
||||
ok( defined( $res->{ns_list} ), 'Has a list of nameservers' );
|
||||
ok( defined( $res->{ds_list} ), 'Has a list of DS records' );
|
||||
|
||||
my @ns_list = map { $_->{ns} } @{ $res->{ns_list} };
|
||||
ok( grep( /d\.nic\.fr/, @ns_list ), 'Has "d.nic.fr" nameserver' );
|
||||
ok( grep( /f\.ext\.nic\.fr/, @ns_list ), 'Has "f.ext.nic.fr" nameserver' );
|
||||
ok( grep( /g\.ext\.nic\.fr/, @ns_list ), 'Has "g.ext.nic.fr" nameserver' );
|
||||
|
||||
my @ip_list = map { $_->{ip} } @{ $res->{ns_list} };
|
||||
ok( grep( /194\.0\.9\.1/, @ip_list ), 'Has "194.0.9.1" ip' ); # d.nic.fr
|
||||
ok( grep( /2001:678:c::1/, @ip_list ), 'Has "2001:678:c::1" ip' );
|
||||
ok( grep( /194\.0\.36\.1/, @ip_list ), 'Has "194.0.36.1" ip' ); # g.ext.nic.fr
|
||||
ok( grep( /2001:678:4c::1/, @ip_list ), 'Has "2001:678:4c::1" ip' );
|
||||
ok( grep( /194\.146\.106\.46/, @ip_list ), 'Has "194.146.106.46" ip' ); # f.ext.nic.fr
|
||||
ok( grep( /2001:67c:1010:11::53/, @ip_list ), 'Has "2001:67c:1010:11::53" ip' );
|
||||
|
||||
my $ds_value = {
|
||||
'algorithm' => 13,
|
||||
'digest' => '1303e8da8fb60db500d5bea1ee5dc9a2bcc93dfe2fc43d346576658feccf5749', # must match case
|
||||
'digtype' => 2,
|
||||
'keytag' => 29133
|
||||
};
|
||||
is( scalar( @{ $res->{ds_list} } ), 1, 'Has only one DS set' );
|
||||
is_deeply( $res->{ds_list}[0], $ds_value, 'Has correct DS values' );
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# start a second test with IPv6 disabled
|
||||
$params->{ipv6} = 0;
|
||||
$hash_id = $rpcapi->start_domain_test( $params );
|
||||
$rpcapi->{db}->claim_test( $hash_id )
|
||||
or BAIL_OUT( "test needs to be claimed before calling run()" );
|
||||
diag "running the agent on test $hash_id";
|
||||
$agent->run($hash_id);
|
||||
|
||||
subtest 'second test has IPv6 disabled' => sub {
|
||||
my $res = $rpcapi->get_test_params( { test_id => $hash_id } );
|
||||
is( $res->{ipv4}, $params->{ipv4}, 'Retrieve the correct "ipv4" value' );
|
||||
is( $res->{ipv6}, $params->{ipv6}, 'Retrieve the correct "ipv6" value' );
|
||||
|
||||
$res = $rpcapi->get_test_results( { id => $hash_id, language => 'en' } );
|
||||
my @msgs = map { $_->{message} } @{ $res->{results} };
|
||||
ok( grep( /IPv6 is disabled/, @msgs ), 'Results contain an "IPv6 is disabled" message' );
|
||||
};
|
||||
|
||||
my $test_history;
|
||||
subtest 'get_test_history' => sub {
|
||||
my $offset = 0;
|
||||
my $limit = 10;
|
||||
my $method_params = {
|
||||
frontend_params => { domain => $params->{domain} },
|
||||
offset => $offset,
|
||||
limit => $limit
|
||||
};
|
||||
|
||||
$test_history = $rpcapi->get_test_history( $method_params );
|
||||
note explain( $test_history );
|
||||
is( scalar( @$test_history ), 2, 'Two tests created' );
|
||||
|
||||
foreach my $res (@$test_history) {
|
||||
is( length($res->{id}), 16, 'Test has 16 characters length hash ID' );
|
||||
is( $res->{undelegated}, JSON::PP::true, 'Test is undelegated' );
|
||||
ok( ! exists $res->{creation_time}, 'Key "creation_time" should be missing' );
|
||||
ok( defined $res->{created_at}, 'Value "created_at" properly defined' );
|
||||
ok( defined $res->{overall_result}, 'Value "overall_result" properly defined' );
|
||||
}
|
||||
|
||||
subtest 'include finished tests only' => sub {
|
||||
# start a thirs test with IPv4 disabled
|
||||
$params->{ipv6} = 1;
|
||||
$params->{ipv4} = 0;
|
||||
|
||||
# create the test, retrieve its id but we don't run it
|
||||
$rpcapi->start_domain_test( $params );
|
||||
( $hash_id ) = $rpcapi->{db}->get_test_request();
|
||||
|
||||
$test_history = $rpcapi->get_test_history( $method_params );
|
||||
note explain( $test_history );
|
||||
is( scalar( @$test_history ), 2, 'Only 2 tests should be retrieved' );
|
||||
|
||||
# now run the test
|
||||
diag "running the agent on test $hash_id";
|
||||
$agent->run( $hash_id );
|
||||
|
||||
$test_history = $rpcapi->get_test_history( $method_params );
|
||||
is( scalar( @$test_history ), 3, 'Now 3 tests should be retrieved' );
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'mock another client (i.e. reuse a previous test)' => sub {
|
||||
$params->{client_id} = 'Another Client';
|
||||
$params->{client_version} = '0.1';
|
||||
|
||||
my $new_hash_id = $rpcapi->start_domain_test( $params );
|
||||
|
||||
is( $new_hash_id, $hash_id, 'Has the same hash than previous test' );
|
||||
|
||||
subtest 'check test_params values' => sub {
|
||||
my $res = $rpcapi->get_test_params( { test_id => "$hash_id" } );
|
||||
# the following values are part of the fingerprint
|
||||
is( $res->{domain}, $params->{domain}, 'Retrieve the correct "domain" value' );
|
||||
is( $res->{profile}, $params->{profile}, 'Retrieve the correct "profile" value' );
|
||||
is( $res->{ipv4}, $params->{ipv4}, 'Retrieve the correct "ipv4" value' );
|
||||
is( $res->{ipv6}, $params->{ipv6}, 'Retrieve the correct "ipv6" value' );
|
||||
is_deeply( $res->{nameservers}, $params->{nameservers}, 'Retrieve the correct "nameservers" value' );
|
||||
is_deeply( $res->{ds_info}, $params->{ds_info}, 'Retrieve the correct "ds_info" value' );
|
||||
|
||||
# both client_id and client_version are different since an old test has been reused
|
||||
isnt( $res->{client_id}, $params->{client_id}, 'The "client_id" value is not the same (which is fine)' );
|
||||
isnt( $res->{client_version}, $params->{client_version}, 'The "client_version" value is not the same (which is fine)' );
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'check historic tests' => sub {
|
||||
# Verifies that delegated and undelegated tests are coded correctly when started
|
||||
# and that the filter option in "get_test_history" works correctly
|
||||
|
||||
my $domain = 'xa';
|
||||
# Non-batch for "start_domain_test":
|
||||
my $params_un1 = { # undelegated, non-batch
|
||||
domain => $domain,
|
||||
nameservers => [
|
||||
{ ns => 'ns2.nic.fr', ip => '192.134.4.1' },
|
||||
],
|
||||
};
|
||||
my $params_un2 = { # undelegated, non-batch
|
||||
domain => $domain,
|
||||
ds_info => [
|
||||
{ keytag => 11627, algorithm => 8, digtype => 2, digest => 'a6cca9e6027ecc80ba0f6d747923127f1d69005fe4f0ec0461bd633482595448' },
|
||||
],
|
||||
};
|
||||
my $params_dn1 = { # delegated, non-batch
|
||||
domain => $domain,
|
||||
};
|
||||
# Batch for "add_batch_job"
|
||||
my $domain2 = 'xb';
|
||||
my $params_ub1 = { # undelegated, batch
|
||||
domains => [ $domain, $domain2 ],
|
||||
test_params => {
|
||||
nameservers => [
|
||||
{ ns => 'ns2.nic.fr', ip => '192.134.4.1' },
|
||||
],
|
||||
},
|
||||
};
|
||||
my $params_ub2 = { # undelegated, batch
|
||||
domains => [ $domain, $domain2 ],
|
||||
test_params => {
|
||||
ds_info => [
|
||||
{ keytag => 11627, algorithm => 8, digtype => 2, digest => 'a6cca9e6027ecc80ba0f6d747923127f1d69005fe4f0ec0461bd633482595448' },
|
||||
],
|
||||
},
|
||||
};
|
||||
my $params_db1 = { # delegated, batch
|
||||
domains => [ $domain, $domain2 ],
|
||||
};
|
||||
# The batch jobs, $params_ub1, $params_ub2 and $params_db1, cannot be run from here due to limitation in the API. See issue #827.
|
||||
|
||||
foreach my $param ($params_un1, $params_un2, $params_dn1) {
|
||||
my $testid = $rpcapi->start_domain_test( $param );
|
||||
ok( $testid, "API start_domain_test ID OK" );
|
||||
$rpcapi->{db}->claim_test( $testid )
|
||||
or BAIL_OUT( "test needs to be claimed before calling run()" );
|
||||
diag "running the agent on test $testid";
|
||||
$agent->run( $testid );
|
||||
is( $rpcapi->test_progress( { test_id => $testid } ), 100 , 'API test_progress -> Test finished' );
|
||||
};
|
||||
|
||||
my $test_history_delegated = $rpcapi->get_test_history(
|
||||
{
|
||||
filter => 'delegated',
|
||||
frontend_params => {
|
||||
domain => $domain,
|
||||
}
|
||||
} );
|
||||
my $test_history_undelegated = $rpcapi->get_test_history(
|
||||
{
|
||||
filter => 'undelegated',
|
||||
frontend_params => {
|
||||
domain => $domain,
|
||||
}
|
||||
} );
|
||||
|
||||
note explain( $test_history_delegated );
|
||||
is( scalar( @$test_history_delegated ), 1, 'One delegated test created' );
|
||||
note explain( $test_history_undelegated );
|
||||
is( scalar( @$test_history_undelegated ), 2, 'Two undelegated tests created' );
|
||||
|
||||
subtest 'domain is case and trailing dot insensitive' => sub {
|
||||
my $test_history_delegated = $rpcapi->get_test_history(
|
||||
{
|
||||
filter => 'delegated',
|
||||
frontend_params => {
|
||||
domain => $domain . '.',
|
||||
}
|
||||
} );
|
||||
my $test_history_undelegated = $rpcapi->get_test_history(
|
||||
{
|
||||
filter => 'undelegated',
|
||||
frontend_params => {
|
||||
domain => ucfirst( $domain ),
|
||||
}
|
||||
} );
|
||||
|
||||
is( scalar( @$test_history_delegated ), 1, 'One delegated test created' );
|
||||
is( scalar( @$test_history_undelegated ), 2, 'Two undelegated tests created' );
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'normalize "domain" column' => sub {
|
||||
my %domains_to_test = (
|
||||
"aFnIc.Fr" => "afnic.fr",
|
||||
"afnic.fr." => "afnic.fr",
|
||||
"aFnic.Fr." => "afnic.fr"
|
||||
);
|
||||
|
||||
my $test_params = {
|
||||
client_id => 'Unit Test',
|
||||
client_version => '1.0',
|
||||
};
|
||||
|
||||
while ( my ($domain, $expected) = each (%domains_to_test) ) {
|
||||
$test_params->{domain} = $domain;
|
||||
|
||||
$hash_id = $rpcapi->start_domain_test( $test_params );
|
||||
my ( $db_domain ) = $dbh->selectrow_array( "SELECT domain FROM test_results WHERE hash_id=?", undef, $hash_id );
|
||||
is( $db_domain, $expected, 'stored domain name is normalized' );
|
||||
}
|
||||
};
|
||||
|
||||
TestUtil::save_datafile( $datafile );
|
||||
|
||||
done_testing();
|
||||
84
zonemaster-backend/t/test_profile.json
Normal file
84
zonemaster-backend/t/test_profile.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"logfilter": {
|
||||
"BASIC":{
|
||||
"IPV6_DISABLED" : [
|
||||
{
|
||||
"when": {
|
||||
"rrtype": [ "SOA", "NS" ]
|
||||
},
|
||||
"set": "INFO"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test_cases": [
|
||||
"address01",
|
||||
"address02",
|
||||
"address03",
|
||||
"basic00",
|
||||
"basic01",
|
||||
"basic02",
|
||||
"basic03",
|
||||
"connectivity01",
|
||||
"connectivity02",
|
||||
"connectivity03",
|
||||
"consistency01",
|
||||
"consistency02",
|
||||
"consistency03",
|
||||
"consistency04",
|
||||
"consistency05",
|
||||
"consistency06",
|
||||
"dnssec01",
|
||||
"dnssec02",
|
||||
"dnssec03",
|
||||
"dnssec04",
|
||||
"dnssec05",
|
||||
"dnssec07",
|
||||
"dnssec06",
|
||||
"dnssec08",
|
||||
"dnssec09",
|
||||
"dnssec10",
|
||||
"dnssec11",
|
||||
"dnssec13",
|
||||
"dnssec14",
|
||||
"dnssec15",
|
||||
"dnssec16",
|
||||
"dnssec17",
|
||||
"dnssec18",
|
||||
"delegation01",
|
||||
"delegation02",
|
||||
"delegation03",
|
||||
"delegation04",
|
||||
"delegation05",
|
||||
"delegation06",
|
||||
"delegation07",
|
||||
"nameserver01",
|
||||
"nameserver02",
|
||||
"nameserver04",
|
||||
"nameserver05",
|
||||
"nameserver06",
|
||||
"nameserver07",
|
||||
"nameserver10",
|
||||
"nameserver11",
|
||||
"nameserver12",
|
||||
"nameserver13",
|
||||
"syntax01",
|
||||
"syntax02",
|
||||
"syntax03",
|
||||
"syntax04",
|
||||
"syntax05",
|
||||
"syntax06",
|
||||
"syntax07",
|
||||
"syntax08",
|
||||
"zone01",
|
||||
"zone02",
|
||||
"zone03",
|
||||
"zone04",
|
||||
"zone05",
|
||||
"zone06",
|
||||
"zone07",
|
||||
"zone08",
|
||||
"zone09",
|
||||
"zone10"
|
||||
]
|
||||
}
|
||||
85
zonemaster-backend/t/test_profile_network_true.json
Normal file
85
zonemaster-backend/t/test_profile_network_true.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"no_network" : false,
|
||||
"logfilter": {
|
||||
"BASIC":{
|
||||
"IPV6_DISABLED" : [
|
||||
{
|
||||
"when": {
|
||||
"rrtype": [ "SOA", "NS" ]
|
||||
},
|
||||
"set": "INFO"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test_cases": [
|
||||
"address01",
|
||||
"address02",
|
||||
"address03",
|
||||
"basic00",
|
||||
"basic01",
|
||||
"basic02",
|
||||
"basic03",
|
||||
"connectivity01",
|
||||
"connectivity02",
|
||||
"connectivity03",
|
||||
"consistency01",
|
||||
"consistency02",
|
||||
"consistency03",
|
||||
"consistency04",
|
||||
"consistency05",
|
||||
"consistency06",
|
||||
"dnssec01",
|
||||
"dnssec02",
|
||||
"dnssec03",
|
||||
"dnssec04",
|
||||
"dnssec05",
|
||||
"dnssec07",
|
||||
"dnssec06",
|
||||
"dnssec08",
|
||||
"dnssec09",
|
||||
"dnssec10",
|
||||
"dnssec11",
|
||||
"dnssec13",
|
||||
"dnssec14",
|
||||
"dnssec15",
|
||||
"dnssec16",
|
||||
"dnssec17",
|
||||
"dnssec18",
|
||||
"delegation01",
|
||||
"delegation02",
|
||||
"delegation03",
|
||||
"delegation04",
|
||||
"delegation05",
|
||||
"delegation06",
|
||||
"delegation07",
|
||||
"nameserver01",
|
||||
"nameserver02",
|
||||
"nameserver04",
|
||||
"nameserver05",
|
||||
"nameserver06",
|
||||
"nameserver07",
|
||||
"nameserver10",
|
||||
"nameserver11",
|
||||
"nameserver12",
|
||||
"nameserver13",
|
||||
"syntax01",
|
||||
"syntax02",
|
||||
"syntax03",
|
||||
"syntax04",
|
||||
"syntax05",
|
||||
"syntax06",
|
||||
"syntax07",
|
||||
"syntax08",
|
||||
"zone01",
|
||||
"zone02",
|
||||
"zone03",
|
||||
"zone04",
|
||||
"zone05",
|
||||
"zone06",
|
||||
"zone07",
|
||||
"zone08",
|
||||
"zone09",
|
||||
"zone10"
|
||||
]
|
||||
}
|
||||
85
zonemaster-backend/t/test_profile_no_network.json
Normal file
85
zonemaster-backend/t/test_profile_no_network.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"no_network" : true,
|
||||
"logfilter": {
|
||||
"BASIC":{
|
||||
"IPV6_DISABLED" : [
|
||||
{
|
||||
"when": {
|
||||
"rrtype": [ "SOA", "NS" ]
|
||||
},
|
||||
"set": "INFO"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test_cases": [
|
||||
"address01",
|
||||
"address02",
|
||||
"address03",
|
||||
"basic00",
|
||||
"basic01",
|
||||
"basic02",
|
||||
"basic03",
|
||||
"connectivity01",
|
||||
"connectivity02",
|
||||
"connectivity03",
|
||||
"consistency01",
|
||||
"consistency02",
|
||||
"consistency03",
|
||||
"consistency04",
|
||||
"consistency05",
|
||||
"consistency06",
|
||||
"dnssec01",
|
||||
"dnssec02",
|
||||
"dnssec03",
|
||||
"dnssec04",
|
||||
"dnssec05",
|
||||
"dnssec07",
|
||||
"dnssec06",
|
||||
"dnssec08",
|
||||
"dnssec09",
|
||||
"dnssec10",
|
||||
"dnssec11",
|
||||
"dnssec13",
|
||||
"dnssec14",
|
||||
"dnssec15",
|
||||
"dnssec16",
|
||||
"dnssec17",
|
||||
"dnssec18",
|
||||
"delegation01",
|
||||
"delegation02",
|
||||
"delegation03",
|
||||
"delegation04",
|
||||
"delegation05",
|
||||
"delegation06",
|
||||
"delegation07",
|
||||
"nameserver01",
|
||||
"nameserver02",
|
||||
"nameserver04",
|
||||
"nameserver05",
|
||||
"nameserver06",
|
||||
"nameserver07",
|
||||
"nameserver10",
|
||||
"nameserver11",
|
||||
"nameserver12",
|
||||
"nameserver13",
|
||||
"syntax01",
|
||||
"syntax02",
|
||||
"syntax03",
|
||||
"syntax04",
|
||||
"syntax05",
|
||||
"syntax06",
|
||||
"syntax07",
|
||||
"syntax08",
|
||||
"zone01",
|
||||
"zone02",
|
||||
"zone03",
|
||||
"zone04",
|
||||
"zone05",
|
||||
"zone06",
|
||||
"zone07",
|
||||
"zone08",
|
||||
"zone09",
|
||||
"zone10"
|
||||
]
|
||||
}
|
||||
289
zonemaster-backend/t/test_validate_syntax.t
Normal file
289
zonemaster-backend/t/test_validate_syntax.t
Normal file
@@ -0,0 +1,289 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.14.2;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
|
||||
use Encode;
|
||||
use File::ShareDir qw[dist_file];
|
||||
use JSON::PP;
|
||||
use File::Temp qw[tempdir];
|
||||
use Zonemaster::Backend::Config;
|
||||
use Zonemaster::Backend::RPCAPI;
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
|
||||
my $config = Zonemaster::Backend::Config->parse( <<EOF );
|
||||
[DB]
|
||||
engine = SQLite
|
||||
|
||||
[SQLITE]
|
||||
database_file = $tempdir/zonemaster.sqlite
|
||||
|
||||
[LANGUAGE]
|
||||
locale = en_US fr_FR da_DK fi_FI nb_NO sl_SI sv_SE
|
||||
EOF
|
||||
|
||||
my $engine = Zonemaster::Backend::RPCAPI->new(
|
||||
{
|
||||
dbtype => $config->DB_engine,
|
||||
config => $config,
|
||||
}
|
||||
);
|
||||
|
||||
sub start_domain_validate_params {
|
||||
return $engine->validate_params( $Zonemaster::Backend::RPCAPI::json_schemas{start_domain_test}, @_ );
|
||||
}
|
||||
|
||||
subtest 'Everything but NoWarnings' => sub {
|
||||
|
||||
my $can_use_threads = eval 'use threads; 1';
|
||||
|
||||
my $frontend_params = {
|
||||
ipv4 => 1,
|
||||
ipv6 => 1,
|
||||
};
|
||||
|
||||
$frontend_params->{nameservers} = [ # list of the namaserves up to 32
|
||||
{ ns => 'ns1.nic.fr', ip => '1.2.3.4' }, # key values pairs representing nameserver => namesterver_ip
|
||||
{ ns => 'ns2.nic.fr', ip => '192.134.4.1' },
|
||||
];
|
||||
|
||||
subtest 'domain present' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => 'afnic.fr'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 );
|
||||
};
|
||||
|
||||
subtest 'consecutive dots' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => 'afnic..fr'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 1 );
|
||||
};
|
||||
|
||||
subtest encode_utf8( 'idn domain=[é]' ) => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => 'é'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest encode_utf8( 'idn domain=[éé]' ) => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => 'éé'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest '253 characters long domain without dot' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => '123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.com'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest '254 characters long domain with trailing dot' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => '123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.com.'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest '254 characters long domain without trailing dot' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => '123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.club'
|
||||
}
|
||||
);
|
||||
|
||||
cmp_ok( scalar @res, '>', 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest '63 characters long domain label' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => '012345678901234567890123456789012345678901234567890123456789-63.fr'
|
||||
}
|
||||
);
|
||||
|
||||
is( scalar @res, 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
subtest '64 characters long domain label' => sub {
|
||||
my @res = start_domain_validate_params(
|
||||
{
|
||||
%$frontend_params, domain => '012345678901234567890123456789012345678901234567890123456789--64.fr'
|
||||
}
|
||||
);
|
||||
|
||||
cmp_ok( scalar @res, '>', 0 )
|
||||
or diag( encode_json @res );
|
||||
};
|
||||
|
||||
#TEST NS
|
||||
$frontend_params->{domain} = 'afnic.fr';
|
||||
$frontend_params->{nameservers}->[0]->{ip} = '1.2.3.4';
|
||||
|
||||
# domain present?
|
||||
$frontend_params->{nameservers}->[0]->{ns} = 'afnic.fr';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, 'domain present' );
|
||||
|
||||
# idn
|
||||
$frontend_params->{nameservers}->[0]->{ns} = 'é';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'idn domain=[é]' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# idn
|
||||
$frontend_params->{nameservers}->[0]->{ns} = 'éé';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'idn domain=[éé]' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# 253 characters long domain without dot
|
||||
$frontend_params->{nameservers}->[0]->{ns} =
|
||||
'123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.com';
|
||||
is(
|
||||
scalar start_domain_validate_params( $frontend_params ), 0,
|
||||
encode_utf8( '253 characters long domain without dot' )
|
||||
) or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# 254 characters long domain with trailing dot
|
||||
$frontend_params->{nameservers}->[0]->{ns} =
|
||||
'123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.com.';
|
||||
is(
|
||||
scalar start_domain_validate_params( $frontend_params ), 0,
|
||||
encode_utf8( '254 characters long domain with trailing dot' )
|
||||
) or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# 254 characters long domain without trailing
|
||||
$frontend_params->{nameservers}->[0]->{ns} =
|
||||
'123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.club';
|
||||
cmp_ok(
|
||||
scalar start_domain_validate_params( $frontend_params ), '>', 0,
|
||||
encode_utf8( '254 characters long domain without trailing dot' )
|
||||
) or diag( encode_jsonstart_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# 63 characters long domain label
|
||||
$frontend_params->{nameservers}->[0]->{ns} = '012345678901234567890123456789012345678901234567890123456789-63.fr';
|
||||
is(
|
||||
scalar start_domain_validate_params( $frontend_params ), 0,
|
||||
encode_utf8( '63 characters long domain label' )
|
||||
) or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# 64 characters long domain label
|
||||
$frontend_params->{nameservers}->[0]->{ns} = '012345678901234567890123456789012345678901234567890123456789-64-.fr';
|
||||
cmp_ok( scalar start_domain_validate_params( $frontend_params ), '>', 0,
|
||||
encode_utf8( '64 characters long domain label' ) )
|
||||
or diag(encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# DELEGATED TEST
|
||||
delete( $frontend_params->{nameservers} );
|
||||
|
||||
$frontend_params->{domain} = 'afnic.fr';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'delegated domain exists' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# IP ADDRESS FORMAT
|
||||
$frontend_params->{domain} = 'afnic.fr';
|
||||
$frontend_params->{nameservers}->[0]->{ns} = 'ns1.nic.fr';
|
||||
|
||||
$frontend_params->{nameservers}->[0]->{ip} = '1.2.3.4';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'Valid IPV4' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{nameservers}->[0]->{ip} = '1.2.3.4444';
|
||||
cmp_ok( scalar start_domain_validate_params( $frontend_params ), '>', 0, encode_utf8( 'Invalid IPV4' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{nameservers}->[0]->{ip} = 'fe80::6ef0:49ff:fe7b:e4bb';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'Valid IPV6' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{nameservers}->[0]->{ip} = 'fe80::6ef0:49ff:fe7b:e4bbffffff';
|
||||
cmp_ok( start_domain_validate_params( $frontend_params ), '>', 0, encode_utf8( 'Invalid IPV6' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
# DS
|
||||
$frontend_params->{domain} = 'afnic.fr';
|
||||
$frontend_params->{nameservers}->[0]->{ns} = 'ns1.nic.fr';
|
||||
$frontend_params->{nameservers}->[0]->{ip} = '1.2.3.4';
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{algorithm} = 1;
|
||||
$frontend_params->{ds_info}->[0]->{digest} = '0123456789012345678901234567890123456789';
|
||||
$frontend_params->{ds_info}->[0]->{digtype} = 1;
|
||||
$frontend_params->{ds_info}->[0]->{keytag} = 5000;
|
||||
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 0, encode_utf8( 'Valid Algorithm Type [numeric format]' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{algorithm} = 'a';
|
||||
$frontend_params->{ds_info}->[0]->{digest} = '0123456789012345678901234567890123456789';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 1, encode_utf8( 'Invalid Algorithm Type' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{algorithm} = 1;
|
||||
$frontend_params->{ds_info}->[0]->{digest} = '01234567890123456789012345678901234567890';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 1, encode_utf8( 'Invalid digest length' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{digest} = 'Z123456789012345678901234567890123456789';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 1, encode_utf8( 'Invalid digest format' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{digest} = '0123456789012345678901234567890123456789';
|
||||
$frontend_params->{ds_info}->[0]->{digtype} = -1;
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 1, encode_utf8( 'Invalid digest type' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{digtype} = 1;
|
||||
$frontend_params->{ds_info}->[0]->{keytag} = 'not a int';
|
||||
is( scalar start_domain_validate_params( $frontend_params ), 1, encode_utf8( 'Invalid keytag' ) )
|
||||
or diag( encode_json start_domain_validate_params( $frontend_params ) );
|
||||
|
||||
$frontend_params->{ds_info}->[0]->{keytag} = 5000;
|
||||
|
||||
{
|
||||
local $frontend_params->{language} = "zz";
|
||||
my @res = start_domain_validate_params( $frontend_params );
|
||||
is( scalar @res, 1, 'Invalid language, "zz" unknown' ) or diag( explain \@res );
|
||||
}
|
||||
|
||||
{
|
||||
local $frontend_params->{language} = "fr-FR";
|
||||
my @res = start_domain_validate_params( $frontend_params );
|
||||
is( scalar @res, 1, 'Invalid language tag syntax' ) or diag( explain \@res );
|
||||
}
|
||||
|
||||
{
|
||||
local $frontend_params->{language} = "nb_NO";
|
||||
my @res = start_domain_validate_params( $frontend_params );
|
||||
is( scalar @res, 1, 'Invalid language tag syntax' ) or diag( explain \@res );
|
||||
}
|
||||
};
|
||||
81
zonemaster-backend/t/translator.t
Normal file
81
zonemaster-backend/t/translator.t
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env perl
|
||||
use v5.16;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More;
|
||||
|
||||
use Locale::Messages qw( LC_ALL );
|
||||
use POSIX qw( setlocale );
|
||||
|
||||
BEGIN {
|
||||
# Set correct locale for translation in case not set in calling environment
|
||||
delete $ENV{"LANG"};
|
||||
delete $ENV{"LANGUAGE"};
|
||||
delete $ENV{"LC_CTYPE"};
|
||||
delete $ENV{"LC_MESSAGES"};
|
||||
setlocale( LC_ALL, "C.UTF-8" );
|
||||
|
||||
use_ok( 'Zonemaster::Backend::Translator' )
|
||||
or BAIL_OUT "Cannot continue without translator module";
|
||||
}
|
||||
|
||||
my $translator = Zonemaster::Backend::Translator->instance();
|
||||
isa_ok $translator, 'Zonemaster::Backend::Translator', "Zonemaster::Backend::Translator->instance()"
|
||||
or BAIL_OUT "Cannot continue without a translator instance";
|
||||
|
||||
subtest 'Basic tests' => sub {
|
||||
isa_ok 'Zonemaster::Backend::Translator', 'Zonemaster::Engine::Translator';
|
||||
|
||||
my $locale = 'fr_FR.UTF-8';
|
||||
ok( $translator->locale( $locale ), "Setting locale to '$locale' works" );
|
||||
};
|
||||
|
||||
subtest 'Testing some translations' => sub {
|
||||
my $message = {
|
||||
module => 'System',
|
||||
testcase => 'Unspecified',
|
||||
timestamp => '0.000778913497924805',
|
||||
level => 'INFO',
|
||||
tag => 'GLOBAL_VERSION',
|
||||
args => { version => 'v5.0.0' }
|
||||
};
|
||||
my $translation = $translator->translate_tag( $message );
|
||||
like $translation, qr/\AUtilisation de la version .* du moteur Zonemaster\.\Z/, 'Translating a GLOBAL_VERSION message tag works';
|
||||
};
|
||||
|
||||
subtest 'Test a message translation from Engine with non-ASCII strings' => sub {
|
||||
my $message = {
|
||||
module => 'Basic',
|
||||
testcase => 'Basic02',
|
||||
timestamp => '4.085114956678410350',
|
||||
level => 'ERROR',
|
||||
tag => 'B02_NS_BROKEN',
|
||||
args => { ns => 'ns1.example' }
|
||||
};
|
||||
my $translation = $translator->translate_tag( $message );
|
||||
|
||||
like $translation, qr/\ARéponse cassée du serveur de noms /, 'Translating a B02_NS_BROKEN message works';
|
||||
like $translation, qr/cass\x{e9}e/, 'Translation is a string of Unicode codepoints, not bytes';
|
||||
};
|
||||
|
||||
subtest 'Test a Backend-specific translation' => sub {
|
||||
my $message = {
|
||||
module => 'Backend',
|
||||
testcase => '',
|
||||
timestamp => '59',
|
||||
level => 'CRITICAL',
|
||||
tag => 'TEST_DIED',
|
||||
args => {}
|
||||
};
|
||||
my $translation = $translator->translate_tag( $message );
|
||||
|
||||
like $translation, qr/\AUne erreur est survenue /, 'Translating a backend-specific TEST_DIED message tag works';
|
||||
};
|
||||
|
||||
subtest 'Test a test case translation with non-ASCII strings' => sub {
|
||||
my $translation = $translator->test_case_description( 'Consistency01' );
|
||||
|
||||
like $translation, qr/\ACoh\x{e9}rence du num\x{e9}ro de s\x{e9}rie/, 'Translating Consistency01 gives a string of Unicode codepoints';
|
||||
};
|
||||
|
||||
done_testing;
|
||||
215
zonemaster-backend/t/validator.t
Normal file
215
zonemaster-backend/t/validator.t
Normal file
@@ -0,0 +1,215 @@
|
||||
#!perl -T
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
use Test::Differences;
|
||||
use Scalar::Util qw( tainted );
|
||||
use JSON::Validator::Schema::Draft7;
|
||||
|
||||
# Get a tainted copy of a string
|
||||
sub taint {
|
||||
my ( $string ) = @_;
|
||||
|
||||
if ( !tainted $0 ) {
|
||||
BAIL_OUT( 'We need $0 to be tainted' );
|
||||
}
|
||||
|
||||
return substr $string . $0, length $0;
|
||||
}
|
||||
|
||||
sub compile_schema {
|
||||
my $jv = JSON::Validator::Schema::Draft7->new->coerce('booleans,numbers,strings')->data(@_);
|
||||
$jv->formats(Zonemaster::Backend::Validator::formats( undef ));
|
||||
return $jv;
|
||||
}
|
||||
|
||||
subtest 'Everything but NoWarnings' => sub {
|
||||
|
||||
use_ok( 'Zonemaster::Backend::Validator', ':untaint' );
|
||||
|
||||
subtest 'ds_info' => sub {
|
||||
my $v = compile_schema( Zonemaster::Backend::Validator->new->ds_info );
|
||||
my $ds_info_40 = { digest => '0' x 40, algorithm => 0, digtype => 0, keytag => 0 };
|
||||
my $ds_info_64 = { digest => '0' x 64, algorithm => 0, digtype => 0, keytag => 0 };
|
||||
eq_or_diff [ $v->validate( $ds_info_40 ) ], [], 'accept ds_info with 40-digit hash';
|
||||
eq_or_diff [ $v->validate( $ds_info_64 ) ], [], 'accept ds_info with 64-digit hash';
|
||||
};
|
||||
|
||||
subtest 'ip_address' => sub {
|
||||
my $v = compile_schema( Zonemaster::Backend::Validator->new->ip_address );
|
||||
eq_or_diff [ $v->validate( '192.168.0.2' ) ], [], 'accept: 192.168.0.2';
|
||||
eq_or_diff [ $v->validate( '2001:db8::1' ) ], [], 'accept: 2001:db8::1';
|
||||
};
|
||||
|
||||
subtest 'untaint_abs_path' => sub {
|
||||
is scalar untaint_abs_path( '/var/db/zonemaster.sqlite' ), '/var/db/zonemaster.sqlite', 'accept: /var/db/zonemaster.sqlite';
|
||||
is scalar untaint_abs_path( 'zonemaster.sqlite' ), undef, 'reject: zonemaster.sqlite';
|
||||
is scalar untaint_abs_path( './zonemaster.sqlite' ), undef, 'reject: ./zonemaster.sqlite';
|
||||
ok !tainted( untaint_abs_path( taint( 'localhost' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_engine_type' => sub {
|
||||
is scalar untaint_engine_type( 'MySQL' ), 'MySQL', 'accept: MySQL';
|
||||
is scalar untaint_engine_type( 'mysql' ), 'mysql', 'accept: mysql';
|
||||
is scalar untaint_engine_type( 'PostgreSQL' ), 'PostgreSQL', 'accept: PostgreSQL';
|
||||
is scalar untaint_engine_type( 'postgresql' ), 'postgresql', 'accept: postgresql';
|
||||
is scalar untaint_engine_type( 'SQLite' ), 'SQLite', 'accept: SQLite';
|
||||
is scalar untaint_engine_type( 'sqlite' ), 'sqlite', 'accept: sqlite';
|
||||
is scalar untaint_engine_type( 'Excel' ), undef, 'reject: Excel';
|
||||
ok !tainted( untaint_engine_type( taint( 'SQLite' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_ip_address' => sub {
|
||||
is scalar untaint_ip_address( '192.0.2.1' ), '192.0.2.1', 'accept: 192.0.2.1';
|
||||
is scalar untaint_ip_address( '192.0.2' ), undef, 'reject: 192.0.2';
|
||||
is scalar untaint_ip_address( '192' ), undef, 'reject: 192';
|
||||
is scalar untaint_ip_address( '192.0.2.1:3306' ), undef, 'reject: 192.0.2.1:3306';
|
||||
is scalar untaint_ip_address( '2001:db8::' ), '2001:db8::', 'accept: 2001:db8::';
|
||||
is scalar untaint_ip_address( '2001:db8::/32' ), undef, 'reject: 2001:db8::/32';
|
||||
is scalar untaint_ip_address( '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff' ), '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff', 'accept: 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff';
|
||||
is scalar untaint_ip_address( '2001:db8:ffff:ffff:ffff:ffff:ffff' ), undef, 'reject: 2001:db8:ffff:ffff:ffff:ffff:ffff';
|
||||
is scalar untaint_ip_address( '2001:db8::255.255.255.254' ), '2001:db8::255.255.255.254', 'accept: 2001:db8::255.255.255.254';
|
||||
is scalar untaint_ip_address( '2001:db8::255.255.255' ), undef, 'reject: 2001:db8::255.255.255';
|
||||
is scalar untaint_ip_address( '::1' ), '::1', 'accept: ::1';
|
||||
is scalar untaint_ip_address( ':::1' ), undef, 'reject: :::1';
|
||||
ok !tainted( untaint_ip_address( taint( '192.0.2.1' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_ldh_domain' => sub {
|
||||
is scalar untaint_ldh_domain( 'localhost' ), 'localhost', 'accept: localhost';
|
||||
is scalar untaint_ldh_domain( 'example.com' ), 'example.com', 'accept: example.com';
|
||||
is scalar untaint_ldh_domain( 'example.com.' ), 'example.com.', 'accept: example.com.';
|
||||
is scalar untaint_ldh_domain( '192.0.2.1' ), '192.0.2.1', 'accept: 192.0.2.1';
|
||||
is scalar untaint_ldh_domain( '0/26.2.0.192.in-addr.arpa' ), '0/26.2.0.192.in-addr.arpa', 'accept: 0/26.2.0.192.in-addr.arpa';
|
||||
is scalar untaint_ldh_domain( '_http._tcp.example.com' ), '_http._tcp.example.com', 'accept: _http._tcp.example.com';
|
||||
is scalar untaint_ldh_domain( '192.0.2.1:3306' ), undef, 'reject: 192.0.2.1:3306';
|
||||
is scalar untaint_ldh_domain( '1!26.2.0.192.in-addr.arpa' ), undef, 'reject: 1!26.2.0.192.in-addr.arpa';
|
||||
is scalar untaint_ldh_domain( '$http.example.com' ), undef, 'reject: $http.example.com';
|
||||
ok !tainted( untaint_ldh_domain( taint( 'localhost' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_locale_tag' => sub {
|
||||
is scalar untaint_locale_tag( 'en_US' ), 'en_US', 'accept: en_US';
|
||||
is scalar untaint_locale_tag( 'en' ), undef, 'reject: en';
|
||||
is scalar untaint_locale_tag( 'English' ), undef, 'reject: English';
|
||||
ok !tainted( untaint_locale_tag( taint( 'en_US' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_mariadb_database' => sub {
|
||||
is scalar untaint_mariadb_database( 'zonemaster' ), 'zonemaster', 'accept: zonemaster';
|
||||
is scalar untaint_mariadb_database( 'ZONEMASTER' ), 'ZONEMASTER', 'accept: ZONEMASTER';
|
||||
is scalar untaint_mariadb_database( 'dollar$' ), 'dollar$', 'accept: dollar$';
|
||||
is scalar untaint_mariadb_database( '$dollar' ), '$dollar', 'accept: $dollar';
|
||||
is scalar untaint_mariadb_database( '0zonemaster' ), '0zonemaster', 'accept: 0zonemaster';
|
||||
is scalar untaint_mariadb_database( 'zm_backend' ), 'zm_backend', 'accept: zm_backend';
|
||||
is scalar untaint_mariadb_database( 'zm backend' ), undef, 'reject: zm backend';
|
||||
is scalar untaint_mariadb_database( 'zm-backend' ), undef, 'reject: zm-backend';
|
||||
is scalar untaint_mariadb_database( '' ), undef, 'reject empty string';
|
||||
is scalar untaint_mariadb_database( 'zönemästër' ), undef, 'reject: zönemästër';
|
||||
is scalar untaint_mariadb_database( 'a' x 65 ), undef, 'reject 65 characters';
|
||||
is scalar untaint_mariadb_database( 'a' x 64 ), 'a' x 64, 'accept 64 characters';
|
||||
ok !tainted( untaint_mariadb_database( taint( 'zonemaster' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_mariadb_user' => sub {
|
||||
is scalar untaint_mariadb_user( 'zonemaster' ), 'zonemaster', 'accept: zonemaster';
|
||||
is scalar untaint_mariadb_user( 'ZONEMASTER' ), 'ZONEMASTER', 'accept: ZONEMASTER';
|
||||
is scalar untaint_mariadb_user( '$dollar' ), '$dollar', 'accept: $dollar';
|
||||
is scalar untaint_mariadb_user( '0zonemaster' ), '0zonemaster', 'accept: 0zonemaster';
|
||||
is scalar untaint_mariadb_user( 'zm_backend' ), 'zm_backend', 'accept: zm_backend';
|
||||
is scalar untaint_mariadb_user( 'zm backend' ), undef, 'reject: zm backend';
|
||||
is scalar untaint_mariadb_user( 'zm-backend' ), undef, 'reject: zm-backend';
|
||||
is scalar untaint_mariadb_user( '' ), undef, 'reject empty string';
|
||||
is scalar untaint_mariadb_user( 'zönemästër' ), undef, 'reject: zönemästër';
|
||||
is scalar untaint_mariadb_user( 'a' x 81 ), undef, 'reject 81 characters';
|
||||
is scalar untaint_mariadb_user( 'a' x 80 ), 'a' x 80, 'accept 80 characters';
|
||||
ok !tainted( untaint_mariadb_user( taint( 'zonemaster' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_password' => sub {
|
||||
is scalar untaint_password( '123456' ), '123456', 'accept: 123456';
|
||||
is scalar untaint_password( 'password' ), 'password', 'accept: password';
|
||||
is scalar untaint_password( '!@#$%^&*<' ), '!@#$%^&*<', 'accept: !@#$%^&*<';
|
||||
is scalar untaint_password( 'Qwertyuiop' ), 'Qwertyuiop', 'accept: Qwertyuiop';
|
||||
is scalar untaint_password( 'battery staple' ), 'battery staple', 'accept: battery staple';
|
||||
is scalar untaint_password( '' ), '', 'accept the empty string';
|
||||
is scalar untaint_password( "\t" ), undef, 'reject tab character';
|
||||
is scalar untaint_password( "\x80" ), undef, 'reject del character';
|
||||
is scalar untaint_password( ' x' ), undef, 'reject initial space';
|
||||
is scalar untaint_password( '<x' ), undef, 'reject initial <';
|
||||
is scalar untaint_password( 'åäö' ), undef, 'reject: åäö';
|
||||
is scalar untaint_password( 'a' x 100 ), 'a' x 100, 'accept 100 characters';
|
||||
is scalar untaint_password( 'a' x 101 ), undef, 'reject 101 characters';
|
||||
ok !tainted( untaint_password( taint( '123456' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_postgresql_ident' => sub {
|
||||
is scalar untaint_postgresql_ident( 'zonemaster' ), 'zonemaster', 'accept: zonemaster';
|
||||
is scalar untaint_postgresql_ident( 'ZONEMASTER' ), 'ZONEMASTER', 'accept: ZONEMASTER';
|
||||
is scalar untaint_postgresql_ident( 'zm_backend' ), 'zm_backend', 'accept: zm_backend';
|
||||
is scalar untaint_postgresql_ident( 'dollar$' ), 'dollar$', 'accept: dollar$';
|
||||
is scalar untaint_postgresql_ident( '$dollar' ), undef, 'reject: $dollar';
|
||||
is scalar untaint_postgresql_ident( 'zm backend' ), undef, 'reject: zm backend';
|
||||
is scalar untaint_postgresql_ident( '0zonemaster' ), undef, 'reject: 0zonemaster';
|
||||
is scalar untaint_postgresql_ident( 'zm-backend' ), undef, 'reject: zm-backend';
|
||||
is scalar untaint_postgresql_ident( '' ), undef, 'reject empty string';
|
||||
is scalar untaint_postgresql_ident( 'zönemästër' ), undef, 'reject: zönemästër';
|
||||
is scalar untaint_postgresql_ident( 'a' x 64 ), undef, 'reject 64 characters';
|
||||
is scalar untaint_postgresql_ident( 'a' x 63 ), 'a' x 63, 'accept 63 characters';
|
||||
ok !tainted( untaint_postgresql_ident( taint( 'zonemaster' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_profile_name' => sub {
|
||||
is scalar untaint_profile_name( 'default' ), 'default', 'accept: default';
|
||||
is scalar untaint_profile_name( '-leading-dash' ), undef, 'reject: -leading-dash';
|
||||
is scalar untaint_profile_name( 'trailing-dash-' ), undef, 'reject: trailing-dash-';
|
||||
is scalar untaint_profile_name( 'middle-dash' ), 'middle-dash', 'accept: middle-dash';
|
||||
is scalar untaint_profile_name( '_leading_underscore' ), undef, 'reject: _leading_underscore';
|
||||
is scalar untaint_profile_name( 'trailing_underscore_' ), undef, 'reject: trailing_underscore_';
|
||||
is scalar untaint_profile_name( 'middle_underscore' ), 'middle_underscore', 'accept: middle_underscore';
|
||||
is scalar untaint_profile_name( '0-leading-digit' ), '0-leading-digit', 'accept: 0-leading-digit';
|
||||
is scalar untaint_profile_name( 'a' ), 'a', 'accept: a';
|
||||
is scalar untaint_profile_name( '-' ), undef, 'reject dash';
|
||||
is scalar untaint_profile_name( '_' ), undef, 'reject underscore';
|
||||
is scalar untaint_profile_name( 'a' x 32 ), 'a' x 32, 'accept 32 characters';
|
||||
is scalar untaint_profile_name( 'a' x 33 ), undef, 'reject 33 characters';
|
||||
ok !tainted( untaint_profile_name( taint( 'default' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_non_negative_int' => sub {
|
||||
is scalar untaint_non_negative_int( '1' ), '1', 'accept: 1';
|
||||
is scalar untaint_non_negative_int( '0' ), '0', 'accept: 0';
|
||||
is scalar untaint_non_negative_int( '99999' ), '99999', 'accept: 99999';
|
||||
is scalar untaint_non_negative_int( '100000' ), undef, 'reject: 100000';
|
||||
is scalar untaint_non_negative_int( '0.5' ), undef, 'reject: 0.5';
|
||||
is scalar untaint_non_negative_int( '-1' ), undef, 'reject: -1';
|
||||
ok !tainted( untaint_non_negative_int( taint( '1' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_strictly_positive_int' => sub {
|
||||
is scalar untaint_strictly_positive_int( '1' ), '1', 'accept: 1';
|
||||
is scalar untaint_strictly_positive_int( '99999' ), '99999', 'accept: 99999';
|
||||
is scalar untaint_strictly_positive_int( '100000' ), undef, 'reject: 100000';
|
||||
is scalar untaint_strictly_positive_int( '0' ), undef, 'reject: 0';
|
||||
is scalar untaint_strictly_positive_int( '0.5' ), undef, 'reject: 0.5';
|
||||
is scalar untaint_strictly_positive_int( '-1' ), undef, 'reject: -1';
|
||||
ok !tainted( untaint_strictly_positive_int( taint( '1' ) ) ), 'launder taint';
|
||||
};
|
||||
|
||||
subtest 'untaint_strictly_positive_millis' => sub {
|
||||
is scalar untaint_strictly_positive_millis( '0.5' ), '0.5', 'accept: 0.5';
|
||||
is scalar untaint_strictly_positive_millis( '0.001' ), '0.001', 'accept: 0.001';
|
||||
is scalar untaint_strictly_positive_millis( '99999.999' ), '99999.999', 'accept: 99999.999';
|
||||
is scalar untaint_strictly_positive_millis( '1' ), '1', 'accept: 1';
|
||||
is scalar untaint_strictly_positive_millis( '99999' ), '99999', 'accept: 99999';
|
||||
is scalar untaint_strictly_positive_millis( '0.0009' ), undef, 'reject: 0.0009';
|
||||
is scalar untaint_strictly_positive_millis( '100000' ), undef, 'reject: 100000';
|
||||
is scalar untaint_strictly_positive_millis( '0' ), undef, 'reject: 0';
|
||||
is scalar untaint_strictly_positive_millis( '0.0' ), undef, 'reject: 0.0';
|
||||
is scalar untaint_strictly_positive_millis( '-1' ), undef, 'reject: -1';
|
||||
ok !tainted( untaint_strictly_positive_millis( taint( '0.5' ) ) ), 'launder taint';
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user