The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use CGI::Wiki::TestLib;
use Test::More;

if ( scalar @CGI::Wiki::TestLib::wiki_info == 0 ) {
    plan skip_all => "no backends configured";
} else {
    plan tests => ( 26 * scalar @CGI::Wiki::TestLib::wiki_info );
}

my $iterator = CGI::Wiki::TestLib->new_wiki_maker;

while ( my $wiki = $iterator->new_wiki ) {
    # Put some test data in.
    ##### Test recent_changes.  We'll write these nodes, in this order,
    ##### with this metadata, sleeping for at least a second in between
    ##### writes, and then run the tests.
    #####
    #####   Node1 (Kake, minor tidying)
    #####   Everyone's Favourite Hobby (nou)
    #####   Another Node

    my $start_time = time;
    do_sleep();
    my $node = "Node1";
    my %node_data = $wiki->retrieve_node( $node );
    $wiki->write_node( $node, @node_data{ qw( content checksum ) },
                       {
                         username  => "Kake",
                         edit_type => "Minor tidying",
                         comment   => "Test",
                       }
                     );
    do_sleep();
    $node = "Everyone's Favourite Hobby";
    %node_data = $wiki->retrieve_node( $node );
    $wiki->write_node( $node, @node_data{ qw( content checksum ) },
                       {
                         comment   => "Test",
                       }
                     );
    do_sleep();
    $node = "Another Node";
    %node_data = $wiki->retrieve_node( $node );
    $wiki->write_node( $node, @node_data{ qw( content checksum ) },
                       {
                         username  => "nou",
                         comment   => "Test",
                       }
                     );

    #####
    ##### Ready to run the tests now.
    #####

    # Test by "in last n days".
    my @nodes = $wiki->list_recent_changes( days => 1 );
    my @nodenames = map { $_->{name} } @nodes;
    my %unique = map { $_ => 1 } @nodenames;
    is_deeply( [sort keys %unique],
               ["Another Node", "Everyone's Favourite Hobby", "Node1"],
               "recent_changes for last 1 day gets the right results" );

    is_deeply( \@nodenames,
               ["Another Node", "Everyone's Favourite Hobby", "Node1"],
               "...in the right order" ); # returns in reverse chron. order

    foreach my $node ( @nodes ) {
        is( ref $node->{metadata}{comment}, "ARRAY",
            "...metadata is returned as a hash of array refs" );
        my @comments = @{$node->{metadata}{comment}};
        is( $comments[0], "Test", "...correct metadata is returned" );
    }

    # Test by "last n nodes changed".
    @nodes = $wiki->list_recent_changes( last_n_changes => 2 );
    @nodenames = map { $_->{name} } @nodes;
    print "# Found nodes: " . join(" ", @nodenames) . "\n";
    is_deeply( \@nodenames,
               ["Another Node", "Everyone's Favourite Hobby"],
               "recent_changes 'last_n_changes' works" );
    eval { $wiki->list_recent_changes( last_n_changes => "foo" ); };
    ok( $@, "...and croaks on bad input" );

    # Test by "since time T".
    @nodes = $wiki->list_recent_changes( since => $start_time );
    @nodenames = sort map { $_->{name} } @nodes;
    is_deeply( \@nodenames,
               ["Another Node", "Everyone's Favourite Hobby", "Node1"],
               "recent_changes 'since' returns the right results" );
    ok( $nodes[0]{last_modified},
        "...and a plausible (not undef or empty) last_modified timestamp");

    # Test metadata_is. (We only actually expect a single result.)
    @nodes = $wiki->list_recent_changes(
        last_n_changes => 2,
        metadata_is    => { username => "Kake" }
    );
    is( scalar @nodes, 1, "metadata_is does constrain the search" );
    is( $nodes[0]{name}, "Node1", "...correctly" );

    # Test metadata_isnt.
    @nodes = $wiki->list_recent_changes(
        last_n_changes => 1,
        metadata_isnt  => { username => "Kake" }
    );
    is( scalar @nodes, 1, "metadata_isnt, too" );
    is( $nodes[0]{name}, "Another Node", "...correctly" );
    print "# " . join(" ", map { $_->{name} } @nodes) . "\n";

    @nodes = $wiki->list_recent_changes(
        last_n_changes => 1,
        metadata_isnt  => { edit_type => "Minor tidying" }
    );
    is( scalar @nodes, 1,
       "metadata_isnt includes nodes where this metadata type isn't set" );
    is( $nodes[0]{name}, "Another Node", "...correctly" );

    eval { @nodes = $wiki->list_recent_changes(
               last_n_changes => 1,
               metadata_isnt  => { arthropod => "millipede" }
           );
    };
    is( $@, "",
  "list_recent_changes doesn't die when metadata_isnt doesn't omit anything" );

    #####
    ##### Write to Another Node again for testing metadata_was and the
    ##### effect of the presence and absence of include_all_changes
    #####

    do_sleep();
    $node = "Another Node";
    %node_data = $wiki->retrieve_node( $node );
    $wiki->write_node( $node, @node_data{ qw( content checksum ) },
                       {
                         username  => "Kake",
                         comment  => "Kake writes the node that nou wrote",
                         edit_type => "Minor tidying",
                       }
                     );

    @nodes = $wiki->list_recent_changes( days => 1 );
    is( scalar @nodes, 3,
        "By default each node returned only once however many times changed" );

    @nodes = $wiki->list_recent_changes( days => 1, include_all_changes => 1 );
    is( scalar @nodes, 4,
        "...returned more than once when 'include_all_changes' set" );

    @nodes = $wiki->list_recent_changes(
        last_n_changes => 5,
        metadata_was => { username => "nou" }
    );
    is( scalar @nodes, 1,
       "metadata_was returns nodes whose current version doesn't match" );
    is( $nodes[0]{name}, "Another Node", "...correctly" );

    ##### Testing metadata_wasnt - Everyone's Favourite Hobby and
    ##### Another Node were both written as *not* minor edits, but
    ##### then a minor edit was made on Another Node.  We expect
    ##### metadata_wasnt to still return Another Node though.

    @nodes = $wiki->list_recent_changes(
        last_n_changes => 5,
        metadata_wasnt => {
                            edit_type => "Minor tidying",
                          },
    );
    is( scalar @nodes, 2,
        "metadata_wasnt returns nodes whose current version matches" );

  SKIP: {
    skip "TODO", 2;

    # Test by "last n nodes added".
    foreach my $node ("Temp Node 1", "Temp Node 2", "Temp Node 3") {
        $wiki->write_node($node, "foo");
        my $slept = sleep(2);
        warn "Slept for less than a second, 'last n added' test may fail"
          unless $slept >= 1;
    }
    @nodes = $wiki->list_recent_changes( last_n_added => 2 );
    @nodenames = map { $_->{name} } @nodes;
    is_deeply( \@nodenames, ["Temp Node 3", "Temp Node 2"],
               "last_n_added works" );
    my $slept = sleep(2);
        warn "Slept for less than a second, 'last n added' test may fail"
          unless $slept >= 1;
    my %node_data = $wiki->retrieve_node("Temp Node 1");
    $wiki->write_node("Temp Node1", @node_data{qw( content checksum )});
    @nodes = $wiki->list_recent_changes( last_n_added => 2 );
    @nodenames = map { $_->{name} } @nodes;
    is_deeply( \@nodenames, ["Temp Node 3", "Temp Node 2"],
               "...still works when we've written to an older node" );

    foreach my $node ("Temp Node 1", "Temp Node 2", "Temp Node 3") {
        $wiki->delete_node($node) or die "Couldn't clean up";
    }
  }
}

sub do_sleep {
my $slept = sleep(2);
warn "Slept for less than a second, test results may be unreliable"
  unless $slept >= 1;
}