#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: t/PluginTester.pm
#
#   Copyright © 2015 Van de Bugger
#
#   This file is part of perl-Dist-Zilla-Role-ErrorLogger.
#
#   perl-Dist-Zilla-Role-ErrorLogger is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free Software Foundation,
#   either version 3 of the License, or (at your option) any later version.
#
#   perl-Dist-Zilla-Role-ErrorLogger is distributed in the hope that it will be useful, but WITHOUT
#   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
#   PURPOSE. See the GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along with
#   perl-Dist-Zilla-Role-ErrorLogger. If not, see <http://www.gnu.org/licenses/>.
#
#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# TODO: Documentation.

package PluginTester;

#   The test is written using `Moose`-based `Test::Routine`. It is not big deal, because we are
#   testing plugin for `Dist::Zilla`, and `Dist-Zilla` is also `Moose`-based.

use autodie ':all';
use namespace::autoclean;

use Test::Routine;

use File::Temp qw{ tempdir };
use Test::Deep qw{ cmp_deeply };
use Test::DZil qw{ dist_ini };
use Test::Fatal;
use Test::More;
use DistZillaTester;

use Try::Tiny;

# `AutoPrereqs` fails to detect these dependencies:
use Software::License::GPL_3::or_later ();      # Used by `dist.ini`.

#   Plugin we are going to test.
has plugin => (
    is          => 'ro',
    isa         => 'Str',
    required    => 1,
    lazy        => 1,
    builder     => '_build_plugin',
);

#   Prefix printed in the beginning of each log message coming from the template.
has prefix => (
    is          => 'ro',
    isa         => 'RegexpRef',
    required    => 1,
    lazy        => 1,
    init_arg    => undef,
    builder     => '_build_prefix',
);

sub _build_prefix {
    my ( $self ) = @_;
    return qr{\A\Q[@{ [ $self->plugin ] }] \E};
};

#   `dist.ini` header (options before the first plugin), hashref.
has ini_head => (
    is          => 'ro',
    isa         => 'HashRef',
    required    => 1,
    lazy        => 1,
    builder     => '_build_ini_header',
);

sub _build_ini_header {
    my ( $self ) = @_;
    return {
        name                => 'Dummy',
        version             => '0.001',
        author              => 'John Doe',
        license             => 'GPL_3::or_later',
        copyright_holder    => 'John Doe',
        copyright_year      => '2007',
    };
};

#   Content of `dist.ini` file, in form acceptable by `Builder->from_config`.
has ini_body => (
    is          => 'ro',
    isa         => 'ArrayRef',
    required    => 1,
    lazy        => 1,
    builder     => '_build_ini_body',
);

#   Test `Dist::Zilla` instance.
has tzil => (
    is          => 'ro',
    isa         => 'Object',
    required    => 1,
    lazy        => 1,
    builder     => '_build_tzil',
    init_arg    => undef,
    handles     => [ 'build', 'log_messages' ],
);

sub _build_tzil {
    my ( $self ) = @_;
    return Builder->from_config(
        { dist_root => tempdir( CLEANUP => 1 ) },
        {
            add_files => {
                'source/lib/Dummy.pm' =>
                    "package Dummy;\n" .
                    "\n" .                  # Blank line for `PkgVersion`.
                    "# ABSTRACT: Dummy\n" .
                    "# VERSION\n" .
                    "1;\n",
                'source/dist.ini' => dist_ini(
                    $self->ini_head,
                    @{ $self->ini_body },
                ),
            },
        },
    );
};

has exception => (
    is          => 'ro',
    isa         => 'Str',
    predicate   => 'has_exception',
    writer      => '_set_exception',
);

#   Expected build result: either undef (default) if build must pass, or reqular expression to
#   match with exception if build must fail.
has exp_exception => (
    is          => 'ro',
    required    => 0,
    default     => undef,
);

#   Expected messages.
has exp_messages => (
    is          => 'ro',
    isa         => 'ArrayRef',
    required    => 0,
);

#   `dzil` messages grepper. Actual messages are grepped (and mapped) before comparing them with
#   expected messages. Default grepper greps only messages from our plugin, and filter out messges
#   coming from other plugins.
has msg_grepper => (
    is          => 'ro',
    isa         => 'CodeRef',
    lazy        => 1,
    builder     => '_build_msg_grepper',
);

sub _build_msg_grepper {
    my ( $self ) = @_;
    return sub { $_ =~ $self->prefix };
};

#   `dzil` message mapper. Actual messages are (grepped and) mapped before comparing them with
#   expected messages. Default mapper strips prefix (plugin name in brackets) from messages.
has msg_mapper => (
    is          => 'ro',
    isa         => 'CodeRef',
    lazy        => 1,
    builder     => '_build_msg_mapper',
);

sub _build_msg_mapper {
    my ( $self ) = @_;
    return sub { $_ =~ s{@{ [ $self->prefix ] }}{}; $_; };
};

sub anno {
    my ( $self ) = @_;
    my $log   = $self->log_messages;
    my $count = @$log;
    my $text  = join( "\n", @$log );
    diag( "Dist::Zilla messages ($count):\n$text\n(end)" );
    if ( $self->has_exception ) {
        my $ex = $self->exception;
        diag( "Exception: $ex" );
    };
};

#   Central test procedure.
test 'build and check' => sub {
    my ( $self ) = @_;
    SKIP: {
        # No plan. Some tests may be executed within `build`, we do not know how many.
        my $exception = exception { $self->build(); };
        if ( defined( $exception ) ) {
            chomp( $exception );
            $self->_set_exception( $exception );
        };
        if ( defined( $self->exp_exception ) ) {
            cmp_deeply( $exception, $self->exp_exception, 'build must fail' ) or do {
                $self->anno();
                skip 'unexpected build result', 1;
            };
        } else {
            is( $exception, undef, 'build must pass' ) or do {
                $self->anno();
                skip 'build unexpectedly fail', 1;
            };
        };
        SKIP: {
            if ( not $self->exp_messages ) {
                skip 'expected messages not specified', 1;
            };
            my @messages = @{ $self->log_messages };
            if ( $self->msg_grepper ) {
                @messages = grep( { $self->msg_grepper->() } @messages );
            };
            if ( $self->msg_mapper ) {
                @messages = map( { $self->msg_mapper->() } @messages );
            };
            cmp_deeply( \@messages, $self->exp_messages, 'dzil messages' ) or do {
                $self->anno();
            };
        };
        $self->check();
    };
    done_testing;
};

1;

# end of file #
