diff --git a/t/protocol_dnsexit2.pl b/t/protocol_dnsexit2.pl index 7b78fac..ac20cac 100644 --- a/t/protocol_dnsexit2.pl +++ b/t/protocol_dnsexit2.pl @@ -6,6 +6,9 @@ use ddclient::t::HTTPD; httpd_required(); +local $ddclient::globals{debug} = 1; +local $ddclient::globals{verbose} = 1; + ddclient::load_json_support('dnsexit2'); httpd()->run(sub { @@ -17,143 +20,160 @@ httpd()->run(sub { })]]; }); -local $ddclient::globals{verbose} = 1; - -sub decode_and_sort_array { - my ($data) = @_; - if (!ref $data) { - $data = decode_json($data); - } - @{$data->{update}} = sort { $a->{type} cmp $b->{type} } @{$data->{update}}; - return $data; +sub cmp_update { + my ($a, $b) = @_; + return $a->{name} cmp $b->{name} || $a->{type} cmp $b->{type}; } -subtest 'Testing nic_dnsexit2_update' => sub { - httpd()->reset(); - local %ddclient::config = ( - 'host.my.example.com' => { - 'usev4' => 'ipv4', - 'wantipv4' => '192.0.2.1', - 'usev6' => 'ipv6', - 'wantipv6' => '2001:db8::1', - 'protocol' => 'dnsexit2', - 'password' => 'mytestingpassword', - 'zone' => 'my.example.com', - 'server' => httpd()->endpoint(), - 'path' => '/update', - 'ttl' => 5 - }); - ddclient::nic_dnsexit2_update(undef, 'host.my.example.com'); - my @requests = httpd()->reset(); - is(scalar(@requests), 1, 'expected number of update requests'); - my $req = shift(@requests); - is($req->method(), 'POST', 'Method is correct'); - is($req->uri()->as_string(), '/update', 'URI contains correct path'); - is($req->header('content-type'), 'application/json', 'Content-Type header is correct'); - is($req->header('accept'), 'application/json', 'Accept header is correct'); - my $got = decode_and_sort_array($req->content()); - my $want = decode_and_sort_array({ - 'domain' => 'my.example.com', - 'apikey' => 'mytestingpassword', - 'update' => [ +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 => [ { - 'type' => 'A', - 'name' => 'host', - 'content' => '192.0.2.1', - 'ttl' => 5, + apikey => 'key', + domain => 'host1.example.com', + update => [ + { + content => '192.0.2.1', + name => '', + ttl => 5, + type => 'A', + }, + ], }, { - 'type' => 'AAAA', - 'name' => 'host', - 'content' => '2001:db8::1', - 'ttl' => 5, - } - ] - }); - is_deeply($got, $want, 'Data is correct'); -}; + apikey => 'key', + domain => 'example.com', + update => [ + { + content => '2001:db8::1', + name => 'host2', + ttl => 10, + type => 'AAAA', + }, + ], + }, + ], + }, +); -subtest 'Testing nic_dnsexit2_update without a zone set' => sub { - httpd()->reset(); - local %ddclient::config = ( - 'myhost.example.com' => { - 'usev4' => 'ipv4', - 'wantipv4' => '192.0.2.1', - 'protocol' => 'dnsexit2', - 'password' => 'anotherpassword', - 'server' => httpd()->endpoint(), - 'path' => '/update-alt', - 'ttl' => 10 - }); - ddclient::nic_dnsexit2_update(undef, 'myhost.example.com'); - my @requests = httpd()->reset(); - is(scalar(@requests), 1, 'expected number of update requests'); - my $req = shift(@requests); - my $got = decode_and_sort_array($req->content()); - my $want = decode_and_sort_array({ - 'domain' => 'myhost.example.com', - 'apikey' => 'anotherpassword', - 'update' => [ - { - 'type' => 'A', - 'name' => '', - 'content' => '192.0.2.1', - 'ttl' => 10, - } - ] - }); - is_deeply($got, $want, 'Data is correct'); -}; - -subtest 'Testing nic_dnsexit2_update with two hostnames, one with a zone and one without' => sub { - httpd()->reset(); - local %ddclient::config = ( - 'host1.example.com' => { - 'usev4' => 'ipv4', - 'wantipv4' => '192.0.2.1', - 'protocol' => 'dnsexit2', - 'password' => 'testingpassword', - 'server' => httpd()->endpoint(), - 'path' => '/update', - 'ttl' => 5 - }, - 'host2.example.com' => { - 'usev6' => 'ipv6', - 'wantipv6' => '2001:db8::1', - 'protocol' => 'dnsexit2', - 'password' => 'testingpassword', - 'server' => httpd()->endpoint(), - 'path' => '/update', - 'ttl' => 10, - 'zone' => 'example.com' +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, 'host1.example.com', 'host2.example.com'); - my @requests = httpd()->reset(); - my @got = map(decode_and_sort_array($_->content()), @requests); - my @want = ( - decode_and_sort_array({ - 'domain' => 'host1.example.com', - 'apikey' => 'testingpassword', - 'update' => [{ - 'type' => 'A', - 'name' => '', - 'content' => '192.0.2.1', - 'ttl' => 5, - }], - }), - decode_and_sort_array({ - 'domain' => 'example.com', - 'apikey' => 'testingpassword', - 'update' => [{ - 'type' => 'AAAA', - 'name' => 'host2', - 'content' => '2001:db8::1', - 'ttl' => 10, - }], - }), - ); - is_deeply(\@got, \@want, 'data is correct'); -}; + 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();