Skip to content

Commit 9dae967

Browse files
Merge pull request #5 from zeroth-blip/chore/configserver-lib-tests-pr
test: add focused unit coverage for ConfigServer helpers
2 parents b24e3c4 + 0629f34 commit 9dae967

8 files changed

Lines changed: 1234 additions & 4 deletions

File tree

.github/tests/unit/abuseip.t

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env perl
2+
3+
use strict;
4+
use warnings;
5+
6+
use FindBin qw($Bin);
7+
use File::Spec;
8+
use File::Temp qw(tempdir);
9+
use Test::More;
10+
use lib "$Bin/../lib";
11+
12+
use TestBootstrap ();
13+
14+
{
15+
package Local::AbuseIPConfig;
16+
17+
sub new {
18+
my ($class, $config) = @_;
19+
return bless { config => $config }, $class;
20+
}
21+
22+
sub config {
23+
my ($self) = @_;
24+
return %{ $self->{config} };
25+
}
26+
}
27+
28+
sub reload_abuseip_module {
29+
my ($config) = @_;
30+
31+
require ConfigServer::Config;
32+
33+
no warnings qw(redefine once);
34+
local *ConfigServer::Config::loadconfig = sub {
35+
return Local::AbuseIPConfig->new($config);
36+
};
37+
38+
delete $INC{'ConfigServer/AbuseIP.pm'};
39+
require ConfigServer::AbuseIP;
40+
return 1;
41+
}
42+
43+
sub build_fake_host_binary {
44+
my ($dir, %opts) = @_;
45+
46+
my $script = File::Spec->catfile($dir, 'fake-host.pl');
47+
my $args_log = File::Spec->catfile($dir, 'fake-host-args.txt');
48+
my $output = $opts{output} // "";
49+
50+
open(my $fh, '>', $script) or die "Unable to create $script: $!";
51+
print {$fh} <<EOF;
52+
#!/usr/bin/env perl
53+
use strict;
54+
use warnings;
55+
56+
open(my \$log, '>', q($args_log)) or die "Unable to write $args_log: \$!";
57+
print {\$log} join("\n", \@ARGV), "\n";
58+
close(\$log);
59+
60+
print <<'OUT';
61+
$output
62+
OUT
63+
EOF
64+
close($fh);
65+
66+
chmod 0755, $script or die "Unable to chmod $script: $!";
67+
return ($script, $args_log);
68+
}
69+
70+
sub slurp_file {
71+
my ($path) = @_;
72+
73+
open(my $fh, '<', $path) or die "Unable to open $path: $!";
74+
my @lines = <$fh>;
75+
close($fh);
76+
chomp @lines;
77+
return @lines;
78+
}
79+
80+
subtest 'abuseip resolves an IPv4 abuse contact and builds the human message' => sub {
81+
my $dir = tempdir(CLEANUP => 1);
82+
my $lookup = '10.2.0.192.abuse-contacts.abusix.org';
83+
my ($host_bin, $args_log) = build_fake_host_binary(
84+
$dir,
85+
output => qq{$lookup has TXT record "abuse\@example.test"\n},
86+
);
87+
88+
reload_abuseip_module({ HOST => $host_bin });
89+
my ($contact, $message) = ConfigServer::AbuseIP::abuseip('192.0.2.10');
90+
91+
is($contact, 'abuse@example.test', 'IPv4 lookup returns the TXT contact value');
92+
like($message, qr/Abuse Contact for 192\.0\.2\.10: \[abuse\@example\.test\]/, 'message includes the IP and abuse contact');
93+
like($message, qr/abusix\.com/, 'message includes the explanatory abuse-contact text');
94+
is_deeply(
95+
[ slurp_file($args_log) ],
96+
['-W', '5', '-t', 'TXT', $lookup],
97+
'host binary receives the expected IPv4 reverse lookup target',
98+
);
99+
};
100+
101+
subtest 'abuseip resolves an IPv6 abuse contact' => sub {
102+
my $dir = tempdir(CLEANUP => 1);
103+
my $lookup = '0.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.abuse-contacts.abusix.org';
104+
my ($host_bin, $args_log) = build_fake_host_binary(
105+
$dir,
106+
output => qq{$lookup has TXT record "ipv6-contact\@example.test"\n},
107+
);
108+
109+
reload_abuseip_module({ HOST => $host_bin });
110+
my ($contact, $message) = ConfigServer::AbuseIP::abuseip('2001:db8::20');
111+
112+
is($contact, 'ipv6-contact@example.test', 'IPv6 lookup returns the TXT contact value');
113+
like($message, qr/Abuse Contact for 2001:db8::20: \[ipv6-contact\@example\.test\]/, 'message includes the IPv6 address and contact');
114+
is_deeply(
115+
[ slurp_file($args_log) ],
116+
['-W', '5', '-t', 'TXT', $lookup],
117+
'host binary receives the expected IPv6 reverse lookup target',
118+
);
119+
};
120+
121+
subtest 'abuseip returns nothing for invalid input and skips the host lookup entirely' => sub {
122+
my $dir = tempdir(CLEANUP => 1);
123+
my ($host_bin, $args_log) = build_fake_host_binary(
124+
$dir,
125+
output => qq{should not be used\n},
126+
);
127+
128+
reload_abuseip_module({ HOST => $host_bin });
129+
my $result = ConfigServer::AbuseIP::abuseip('not-an-ip');
130+
131+
ok(!$result, 'invalid input returns a false value');
132+
ok(!-e $args_log, 'host binary is not invoked for invalid input');
133+
};
134+
135+
subtest 'abuseip returns nothing when the lookup output contains no quoted contact' => sub {
136+
my $dir = tempdir(CLEANUP => 1);
137+
my $lookup = '10.2.0.192.abuse-contacts.abusix.org';
138+
my ($host_bin, $args_log) = build_fake_host_binary(
139+
$dir,
140+
output => qq{$lookup lookup failed\n},
141+
);
142+
143+
reload_abuseip_module({ HOST => $host_bin });
144+
my $result = ConfigServer::AbuseIP::abuseip('192.0.2.10');
145+
146+
ok(!$result, 'missing TXT contact produces a false value');
147+
is_deeply(
148+
[ slurp_file($args_log) ],
149+
['-W', '5', '-t', 'TXT', $lookup],
150+
'host lookup still runs even when no contact is found',
151+
);
152+
};
153+
154+
done_testing;

.github/tests/unit/getips.t

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env perl
2+
3+
use strict;
4+
use warnings;
5+
6+
use FindBin qw($Bin);
7+
use File::Spec;
8+
use File::Temp qw(tempdir);
9+
use Test::More;
10+
use lib "$Bin/../lib";
11+
12+
use TestBootstrap ();
13+
14+
{
15+
package Local::GetIPsConfig;
16+
17+
sub new {
18+
my ($class, $config) = @_;
19+
return bless { config => $config }, $class;
20+
}
21+
22+
sub config {
23+
my ($self) = @_;
24+
return %{ $self->{config} };
25+
}
26+
}
27+
28+
sub reload_getips_module {
29+
my ($config) = @_;
30+
31+
require ConfigServer::Config;
32+
33+
no warnings qw(redefine once);
34+
local *ConfigServer::Config::loadconfig = sub {
35+
return Local::GetIPsConfig->new($config);
36+
};
37+
38+
delete $INC{'ConfigServer/GetIPs.pm'};
39+
require ConfigServer::GetIPs;
40+
return 1;
41+
}
42+
43+
sub build_fake_host_binary {
44+
my ($dir, %opts) = @_;
45+
46+
my $script = File::Spec->catfile($dir, 'fake-host.pl');
47+
my $args_log = File::Spec->catfile($dir, 'fake-host-args.txt');
48+
my $output = $opts{output} // "";
49+
50+
open(my $fh, '>', $script) or die "Unable to create $script: $!";
51+
print {$fh} <<EOF;
52+
#!/usr/bin/env perl
53+
use strict;
54+
use warnings;
55+
56+
open(my \$log, '>', q($args_log)) or die "Unable to write $args_log: \$!";
57+
print {\$log} join("\n", \@ARGV), "\n";
58+
close(\$log);
59+
60+
print <<'OUT';
61+
$output
62+
OUT
63+
EOF
64+
close($fh);
65+
66+
chmod 0755, $script or die "Unable to chmod $script: $!";
67+
return ($script, $args_log);
68+
}
69+
70+
sub slurp_file {
71+
my ($path) = @_;
72+
73+
open(my $fh, '<', $path) or die "Unable to open $path: $!";
74+
my @lines = <$fh>;
75+
close($fh);
76+
chomp @lines;
77+
return @lines;
78+
}
79+
80+
subtest 'getips uses the configured host binary and extracts IPv4 and IPv6 results' => sub {
81+
my $dir = tempdir(CLEANUP => 1);
82+
my ($host_bin, $args_log) = build_fake_host_binary(
83+
$dir,
84+
output => <<'OUT',
85+
example.test has address 192.0.2.10
86+
example.test has IPv6 address 2001:db8::20
87+
this line contains no address
88+
OUT
89+
);
90+
91+
reload_getips_module({ HOST => $host_bin });
92+
my @ips = ConfigServer::GetIPs::getips('example.test');
93+
94+
is_deeply(
95+
\@ips,
96+
['192.0.2.10', '2001:db8::20'],
97+
'host command output is parsed into IPv4 and IPv6 results in order',
98+
);
99+
100+
is_deeply(
101+
[ slurp_file($args_log) ],
102+
['-W', '5', 'example.test'],
103+
'host binary is invoked with the expected timeout arguments and hostname',
104+
);
105+
};
106+
107+
subtest 'getips returns an empty list when the host command yields no addresses' => sub {
108+
my $dir = tempdir(CLEANUP => 1);
109+
my ($host_bin, undef) = build_fake_host_binary(
110+
$dir,
111+
output => <<'OUT',
112+
example.test has no A record
113+
example.test is unreachable
114+
OUT
115+
);
116+
117+
reload_getips_module({ HOST => $host_bin });
118+
my @ips = ConfigServer::GetIPs::getips('example.test');
119+
120+
is_deeply(\@ips, [], 'non-address output produces no results');
121+
};
122+
123+
subtest 'getips falls back to local resolver logic when no host binary is available' => sub {
124+
reload_getips_module({ HOST => '/definitely/missing/host-binary' });
125+
my @ips = ConfigServer::GetIPs::getips('localhost');
126+
127+
ok(@ips >= 1, 'resolver fallback returns at least one address for localhost');
128+
ok(
129+
scalar(grep { $_ eq '127.0.0.1' || $_ eq '::1' } @ips),
130+
'resolver fallback includes a loopback address',
131+
);
132+
};
133+
134+
done_testing;

0 commit comments

Comments
 (0)