use strict;
use warnings;
use blib;
use Test::More tests => 43;
use constant valid_mfrom_identity => ( identity => 'fred@example.com' );
use constant valid_ip_address => ( ip_address => '192.168.0.1' );
#### Class Compilation ####
BEGIN { use_ok('Mail::SPF::Request') }
#### Basic Instantiation ####
{
my $request = eval { Mail::SPF::Request->new(
versions => [1, 2],
scope => 'mfrom',
identity => 'fred@example.com',
ip_address => '192.168.0.1',
helo_identity
=> 'mta.example.com'
) };
$@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Basic request object')
or BAIL_OUT("Basic request instantiation failed: $@");
# Have options been interpreted correctly?
is_deeply([$request->versions], [1, 2], 'Basic request versions()');
is($request->scope, 'mfrom', 'Basic request scope()');
is($request->authority_domain, 'example.com', 'Basic request authority_domain()');
is($request->identity, 'fred@example.com', 'Basic request identity()');
is($request->domain, 'example.com', 'Basic request domain()');
is($request->localpart, 'fred', 'Basic request localpart()');
my $ip_address = $request->ip_address;
isa_ok($ip_address, 'NetAddr::IP', 'Basic request ip_address()');
is($ip_address, '192.168.0.1/32', 'Basic request ip_address()');
is($ip_address->version, 4, 'Basic request ip_address() IP version');
my $ip_address_v6 = $request->ip_address_v6;
isa_ok($ip_address_v6, 'NetAddr::IP', 'Basic request ip_address_v6()');
is($ip_address_v6, NetAddr::IP->new('::ffff:192.168.0.1'), 'Basic request ip_address_v6()');
is($ip_address_v6->version, 6, 'Basic request ip_address_v6() IP version');
is($request->helo_identity, 'mta.example.com', 'Basic request helo_identity()');
# Request object cloning:
my $request_clone = eval { $request->new( ip_address => '192.168.0.254' ) };
isa_ok($request_clone, 'Mail::SPF::Request', 'Clone request object');
is($request_clone->identity, 'fred@example.com', 'Clone request inherited identity()');
is($request_clone->ip_address, '192.168.0.254/32', 'Clone request override ip_address()');
}
#### Minimally Parameterized MAIL FROM Request ####
{
my $request = eval { Mail::SPF::Request->new(
identity => 'fred@example.com',
ip_address => '192.168.0.1'
) };
$@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Minimal MAIL FROM request object')
or BAIL_OUT("Minimal MAIL FROM request instantiation failed: $@");
# Have omitted options been deduced correctly?
is_deeply([$request->versions], [1, 2], 'Minimal MAIL FROM request versions()');
is($request->scope, 'mfrom', 'Minimal MAIL FROM request scope()');
is($request->authority_domain, 'example.com', 'Minimal MAIL FROM request authority_domain()');
is($request->helo_identity, undef, 'Minimal MAIL FROM request helo_identity()');
}
#### Minimally Parameterized HELO Request ####
{
my $request = eval { Mail::SPF::Request->new(
scope => 'helo',
identity => 'mta.example.com',
valid_ip_address
) };
$@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Minimal HELO request object')
or BAIL_OUT("Minimal HELO request instantiation failed: $@");
# Have omitted options been deduced correctly?
is_deeply([$request->versions], [1], 'Minimal HELO request versions()');
is($request->authority_domain, 'mta.example.com', 'Minimal HELO request authority_domain()');
is($request->localpart, 'postmaster', 'Minimal HELO request default localpart()');
is($request->helo_identity, 'mta.example.com', 'Minimal HELO request helo_identity()');
}
#### Versions Validation ####
{
my $request;
$request = Mail::SPF::Request->new(
versions => 1,
valid_mfrom_identity,
valid_ip_address
);
is_deeply([$request->versions], [1], 'versions => $string supported');
eval { Mail::SPF::Request->new(
versions => {}, # Illegal versions option type!
valid_mfrom_identity,
valid_ip_address
) };
isa_ok($@, 'Mail::SPF::EInvalidOptionValue', 'versions => $non_string_or_array illegal');
eval { Mail::SPF::Request->new(
versions => [1, 666], # Illegal version number!
valid_mfrom_identity,
valid_ip_address
) };
isa_ok($@, 'Mail::SPF::EInvalidOptionValue', 'Detect illegal versions');
$request = Mail::SPF::Request->new(
versions => [1, 2],
scope => 'helo',
identity => 'mta.example.com',
valid_ip_address
);
is_deeply([$request->versions], [1], 'Drop versions irrelevant for scope');
}
#### Scope Validation ####
{
eval { Mail::SPF::Request->new(
scope => 'foo',
valid_mfrom_identity,
valid_ip_address
) };
isa_ok($@, 'Mail::SPF::EInvalidScope', 'Detect invalid scope');
eval { Mail::SPF::Request->new(
versions => 1,
scope => 'pra',
valid_mfrom_identity,
valid_ip_address
) };
isa_ok($@, 'Mail::SPF::EInvalidScope', 'Detect invalid scope for versions');
}
#### Identity Validation ####
{
my $request;
eval { Mail::SPF::Request->new(
valid_ip_address
) };
isa_ok($@, 'Mail::SPF::EOptionRequired', 'Detect missing identity option');
$request = Mail::SPF::Request->new(
scope => 'mfrom',
identity => 'mta.example.com', # Empty MAIL FROM, supply HELO domain.
valid_ip_address
);
is($request->domain, 'mta.example.com', 'Extract domain from identity correctly');
is($request->localpart, 'postmaster', 'Default "postmaster" localpart');
}
#### IP Address Validation ####
{
my $request;
eval { Mail::SPF::Request->new(
valid_mfrom_identity
) };
isa_ok($@, 'Mail::SPF::EOptionRequired', 'Detect missing ip_address option');
my $ip_address = NetAddr::IP->new('192.168.0.1');
$request = Mail::SPF::Request->new(
valid_mfrom_identity,
ip_address => $ip_address
);
is($request->ip_address, $ip_address, 'Accept NetAddr::IP object for ip_address');
$request = Mail::SPF::Request->new(
valid_mfrom_identity,
ip_address => '::ffff:192.168.0.1'
);
is($request->ip_address, '192.168.0.1/32', 'Treat IPv4-mapped IPv6 address as IPv6 address');
}
#### Custom Request State ####
{
my $request = Mail::SPF::Request->new(
valid_mfrom_identity,
valid_ip_address
);
is($request->state('uninitialized'), undef, 'Read uninitialized state field');
$request->state('foo', 'bar');
is($request->state('foo'), 'bar', 'Write and read state field');
my $request_clone = $request->new(); # Clone request object.
$request_clone->state('foo', 'boo');
is($request->state('foo'), 'bar', 'Original state unaffected when modifying clone state');
}