ddclient/t/protocol_dnsexit2.pl
Richard Hansen 3b73350541 tests: dnsexit2 Convert tests to table-driven
This makes the tests easier to read and extend.
2025-01-09 19:33:57 -05:00

179 lines
5.2 KiB
Perl

use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require JSON::PP; 1; } or plan(skip_all => $@); JSON::PP->import(); }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t::HTTPD;
httpd_required();
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
ddclient::load_json_support('dnsexit2');
httpd()->run(sub {
my ($req) = @_;
return undef if $req->uri()->path() eq '/control';
return [200, ['Content-Type' => 'application/json'], [encode_json({
code => 0,
message => 'Success'
})]];
});
sub cmp_update {
my ($a, $b) = @_;
return $a->{name} cmp $b->{name} || $a->{type} cmp $b->{type};
}
sub sort_updates {
my ($req) = @_;
return {
%$req,
update => [sort({ cmp_update($a, $b); } @{$req->{update}})],
};
}
sub sort_reqs {
my @reqs = map(sort_updates($_), @_);
my @sorted = sort({
my $ret = $a->{domain} cmp $b->{domain};
$ret = @{$a->{update}} <=> @{$b->{update}} if !$ret;
my $i = 0;
while (!$ret && $i < @{$a->{update}} && $i < @{$b->{update}}) {
$ret = cmp_update($a->{update}[$i], $b->{update}[$i]);
}
return $ret;
} @reqs);
return @sorted;
}
my @test_cases = (
{
desc => 'both IPv4 and IPv6 are updated together',
cfg => {
'host.my.example.com' => {
ttl => 5,
wantipv4 => '192.0.2.1',
wantipv6 => '2001:db8::1',
zone => 'my.example.com',
},
},
want => [{
apikey => 'key',
domain => 'my.example.com',
update => [
{
content => '192.0.2.1',
name => 'host',
ttl => 5,
type => 'A',
},
{
content => '2001:db8::1',
name => 'host',
ttl => 5,
type => 'AAAA',
},
],
}],
},
{
desc => 'zone defaults to host',
cfg => {
'host.my.example.com' => {
ttl => 10,
wantipv4 => '192.0.2.1',
},
},
want => [{
apikey => 'key',
domain => 'host.my.example.com',
update => [
{
content => '192.0.2.1',
name => '',
ttl => 10,
type => 'A',
},
],
}],
},
{
desc => 'two hosts, different zones',
cfg => {
'host1.example.com' => {
ttl => 5,
wantipv4 => '192.0.2.1',
# 'zone' intentionally not set, so it will default to 'host1.example.com'.
},
'host2.example.com' => {
ttl => 10,
wantipv6 => '2001:db8::1',
zone => 'example.com',
},
},
want => [
{
apikey => 'key',
domain => 'host1.example.com',
update => [
{
content => '192.0.2.1',
name => '',
ttl => 5,
type => 'A',
},
],
},
{
apikey => 'key',
domain => 'example.com',
update => [
{
content => '2001:db8::1',
name => 'host2',
ttl => 10,
type => 'AAAA',
},
],
},
],
},
);
for my $tc (@test_cases) {
subtest($tc->{desc} => sub {
local $ddclient::_l = ddclient::pushlogctx($tc->{desc});
local %ddclient::config = ();
my @hosts = keys(%{$tc->{cfg}});
for my $h (@hosts) {
$ddclient::config{$h} = {
password => 'key',
path => '/update',
server => httpd()->endpoint(),
%{$tc->{cfg}{$h}},
};
}
ddclient::nic_dnsexit2_update(undef, @hosts);
my @requests = httpd()->reset();
my @got;
for (my $i = 0; $i < @requests; $i++) {
subtest("request $i" => sub {
my $req = $requests[$i];
is($req->method(), 'POST', 'method is POST');
is($req->uri()->as_string(), '/update', 'path is /update');
is($req->header('content-type'), 'application/json', 'Content-Type is JSON');
is($req->header('accept'), 'application/json', 'Accept is JSON');
my $got = decode_json($req->content());
is(ref($got), 'HASH', 'request content is a JSON object');
is(ref($got->{update}), 'ARRAY', 'JSON object has array "update" property');
push(@got, $got);
});
}
@got = sort_reqs(@got);
my @want = sort_reqs(@{$tc->{want}});
is_deeply(\@got, \@want, 'request objects match');
});
}
done_testing();