- 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>
426 lines
16 KiB
Perl
426 lines
16 KiB
Perl
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();
|