Email-Stuffer-TestLinks
view release on metacpan or search on metacpan
lib/Email/Stuffer/TestLinks.pm view on Meta::CPAN
use Future::Utils qw( fmap_void );
=head1 SYNOPSIS
use Email::Stuffer::TestLinks;
=head1 NAME
Email::Stuffer::TestLinks - validates links in HTML emails sent by
Email::Stuffer>send_or_die()
=head1 DESCRIPTION
When this module is included in a test, it parses http links (<a href="xyz">...</a>)
and image links (<img src="xyz">) in every email sent through Email::Stuffer->send_or_die().
Each URI must be get a successful response code (200 range).
Page title must not contain 'error' or 'not found' for text/html content.
Image links must return an image content type.
=cut
install_modifier 'Email::Stuffer', after => send_or_die => sub {
my $self = shift;
my %urls;
$self->email->walk_parts(
sub {
my ($part) = @_;
return unless ($part->content_type && $part->content_type =~ /text\/html/i);
my $dom = Mojo::DOM->new($part->body);
push @{$urls{http}}, $dom->find('a')->map(attr => 'href')->compact->grep(sub { $_ !~ /^mailto:/ })->uniq->to_array->@*;
push @{$urls{image}}, $dom->find('img')->map(attr => 'src')->compact->uniq->to_array->@*;
});
my @data = map {
my $type = $_;
map { [$type, $_] } $urls{$type}->@*
} keys %urls;
my $loop = IO::Async::Loop->new();
$loop->add(my $http = Net::Async::HTTP->new(max_connections_per_host => 3));
(
fmap_void {
my ($type, $url) = @$_;
my $uri = URI->new($url);
unless ($uri->scheme) {
fail "$type link $url is an invalid uri";
return Future->done;
}
$http->GET(URI->new($uri))->then(
sub {
my $response = shift;
return Future->fail("Response code was " . $response->code) if ($response->code !~ /^2\d\d/);
if ($response->content_type eq 'text/html') {
my $dom = Mojo::DOM->new($response->decoded_content);
if (my $title = $dom->at('title')) {
return Future->fail("Page title contains text '$1'") if $title->text =~ /(error|not found)/i;
}
}
if ($type eq 'image') {
return Future->fail("Unexpected content type: " . $response->content_type) unless $response->content_type =~ /^image\//;
}
return Future->done;
}
)->transform(
done => sub {
pass "$type link works ($url)";
},
fail => sub {
my $failure = shift;
fail "$type link $url does not work - $failure";
}
)->else(sub { Future->done })
}
foreach => \@data,
concurrent => 10
)->get;
};
1;
( run in 2.004 seconds using v1.01-cache-2.11-cpan-75ffa21a3d4 )