Punk-Feed

 view release on metacpan or  search on metacpan

t/10-atom.t  view on Meta::CPAN

#!perl
use 5.010;
use strict;
use warnings;
use Test::More;
use POSIX ();
use Punk ();
use Punk::Plugin::Feed ();

our @ROWS;
our %OPTS;
my $N = 0;

# An application with one feed returning @main::ROWS, compiled and built.
sub app {
    my ($rows, %opts) = @_;
    local @ROWS = @$rows;
    local %OPTS = (title => 'Example', %opts);
    my $pkg = 'FeedAtom' . ++$N;
    my $extra = delete $OPTS{_extra} || '';
    eval "package $pkg;\nuse Punk;\nuse Punk::Plugin::Feed;\n"
       . "host 'https://example.com';\n"
       . "plugin 'Feed' => { \%main::OPTS };\n"
       . "feed sub { \@main::ROWS };\n"
       . "$extra\n1" or die $@;
    $pkg->to_app;
    return $pkg->punk_app;
}

sub atom { Punk::Plugin::Feed::_doc($_[0], $_[1], 'atom') }

my @ONE = ({ loc => '/p/1', title => 'First', updated => '2019-03-04T05:06:07Z' });

# ---- the document's frame --------------------------------------------------

{
    my $d = atom(app(\@ONE));

    is(index($d, '<?xml version="1.0" encoding="UTF-8"?>'), 0,
        'the prolog is the very first bytes - a BOM or a leading newline '
      . 'makes the document invalid');
    like($d, qr{<feed xmlns="http://www\.w3\.org/2005/Atom">},
        'the Atom namespace');
    like($d, qr{</feed>\s*\z}, 'and it closes');

    # everything Atom makes mandatory, at the feed level
    like($d, qr{<id>https://example\.com/feed\.xml</id>}, 'a feed id');
    like($d, qr{<title>Example</title>},                  'a feed title');
    like($d, qr{<updated>2019-03-04T05:06:07Z</updated>}, 'a feed updated');

    like($d, qr{<link rel="self" type="application/atom\+xml" href="https://example\.com/feed\.xml"/>},
        'a self link, naming the format it is');
    like($d, qr{<link rel="alternate" type="text/html" href="https://example\.com/"/>},
        'an alternate link to the site');
    like($d, qr{<generator uri="[^"]+" version="[^"]+">Punk::Feed</generator>},
        'a generator');

    # and at the entry level
    like($d, qr{<entry>.*<id>https://example\.com/p/1</id>.*</entry>}s,
        'an entry id, defaulting to the absolute URL');
    like($d, qr{<title>First</title>},                    'an entry title');
    like($d, qr{<entry>.*<updated>2019-03-04T05:06:07Z</updated>.*</entry>}s,
        'an entry updated');
}

# ---- the feed date is the newest entry's, never now -----------------------

{
    my $d = atom(app([
        { loc => '/old', title => 'o', updated => '2018-01-01T00:00:00Z' },
        { loc => '/new', title => 'n', updated => '2019-03-04T05:06:07Z' },
    ]));
    my ($feed_updated) = $d =~ m{<updated>([^<]+)</updated>};
    is($feed_updated, '2019-03-04T05:06:07Z',
        'the feed updated is the newest entry, not the build time');

    my $now_year = POSIX::strftime('%Y', gmtime);
    isnt(substr($feed_updated, 0, 4), $now_year,
        "  and demonstrably not now (this year is $now_year)");
}

# A rebuild that found nothing new must produce the same bytes, or every reader
# records a change on every TTL.
{
    my $a = atom(app(\@ONE));
    my $b = atom(app(\@ONE));
    is($a, $b, 'two builds of the same entries are byte-identical');
    is(Punk::Plugin::Feed::_etag(app(\@ONE), undef, 'atom'),
       Punk::Plugin::Feed::_etag(app(\@ONE), undef, 'atom'),
       '  and so are their ETags');
}

{
    my $x = Punk::Plugin::Feed::_etag(app(\@ONE), undef, 'atom');
    my $y = Punk::Plugin::Feed::_etag(
        app([ { loc => '/p/1', title => 'Changed',
                updated => '2019-03-04T05:06:07Z' } ]), undef, 'atom');
    isnt($x, $y, 'a changed entry changes the ETag');
    like($x, qr/\A"[0-9a-f]+-[0-9a-f]{8}"\z/, 'the ETag is a quoted strong tag');
}



( run in 0.898 second using v1.01-cache-2.11-cpan-8dfa8b56332 )