App-Sqitch

 view release on metacpan or  search on metacpan

t/add.t  view on Meta::CPAN

#!/usr/bin/perl -w

use strict;
use warnings;
use utf8;
use Test::More tests => 243;
#use Test::More 'no_plan';
use App::Sqitch;
use App::Sqitch::Target;
use Locale::TextDomain qw(App-Sqitch);
use Path::Class;
use Test::Exception;
use Test::Warn;
use Test::Dir;
use File::Temp 'tempdir';
use Test::File qw(file_not_exists_ok file_exists_ok);
use Test::File::Contents 0.05;
use File::Path qw(make_path remove_tree);
use Test::NoWarnings 0.083;
use lib 't/lib';
use MockOutput;
use TestConfig;

my $CLASS = 'App::Sqitch::Command::add';

my $config = TestConfig->new(
    'core.engine' => 'pg',
    'core.top_dir' => dir('test-add')->stringify,
);
ok my $sqitch = App::Sqitch->new(config => $config),
    'Load a sqitch sqitch object';

isa_ok my $add = App::Sqitch::Command->load({
    sqitch  => $sqitch,
    command => 'add',
    config  => $config,
    args    => [],
}), $CLASS, 'add command';
my $target = $add->default_target;

sub dep($$) {
    my $dep = App::Sqitch::Plan::Depend->new(
        %{ App::Sqitch::Plan::Depend->parse( $_[1] ) },
        plan      => $add->default_target->plan,
        conflicts => $_[0],
    );
    $dep->project;
    return $dep;
}

can_ok $CLASS, qw(
    options
    requires
    conflicts
    variables
    template_name
    template_directory
    with_scripts
    templates
    open_editor
    configure
    execute
    _config_templates
    all_templates
    _slurp
    _add
    does
);

ok $CLASS->does("App::Sqitch::Role::ContextCommand"),
    "$CLASS does ContextCommand";

is_deeply [$CLASS->options], [qw(
    change-name|change|c=s
    requires|r=s@
    conflicts|x=s@

t/add.t  view on Meta::CPAN

is_deeply $add->all_templates($tname), {
    deploy => $tmp_dir->file('deploy/pg.tmpl'),
    whatev => $tmp_dir->file('whatev/pg.tmpl'),
    revert => file('etc/templates/revert/pg.tmpl'),
    verify => file('etc/templates/verify/pg.tmpl'),
}, 'Template dir files should override others';

# Add in configured files.
ok $add = $CLASS->new(
    sqitch => $sqitch,
    template_directory => $tmp_dir,
    templates => {
        foo => file('foo'),
        verify => file('verify'),
        deploy => file('deploy'),
    },
), 'Add object with configured templates';

is_deeply $add->all_templates($tname), {
    deploy => file('deploy'),
    verify => file('verify'),
    foo => file('foo'),
    whatev => $tmp_dir->file('whatev/pg.tmpl'),
    revert => file('etc/templates/revert/pg.tmpl'),
}, 'Template dir files should override others';

# Should die when missing files.
$sysdir = $usrdir;
for my $script (qw(deploy revert verify)) {
    ok $add = $CLASS->new(
        sqitch => $sqitch,
        with_scripts => { deploy => 0, revert => 0, verify => 0, $script => 1 },
    ), "Add object requiring $script template";

    throws_ok { $add->all_templates($tname) } 'App::Sqitch::X',
        "Should get error for missing $script template";
    is $@->ident, 'add', qq{Missing $script template ident should be "add"};
    is $@->message, __x(
        'Cannot find {script} template',
        script => $script,
    ), "Missing $script template message should be correct";
}

##############################################################################
# Test _slurp().
$tmpl = file(qw(etc templates deploy pg.tmpl));
is $ { $add->_slurp($tmpl)}, contents_of $tmpl,
    '_slurp() should load a reference to file contents';

##############################################################################
# Test _add().
my $test_add = sub {
    my $engine = shift;
    make_path 'test-add';
    my $fn = $target->plan_file;
    open my $fh, '>', $fn or die "Cannot open $fn: $!";
    say $fh "%project=add\n\n";
    close $fh or die "Error closing $fn: $!";
    END { remove_tree 'test-add' };
    my $out = file 'test-add', 'sqitch_change_test.sql';
    file_not_exists_ok $out;
    ok my $add = $CLASS->new(
        sqitch => $sqitch,
        template_directory => $tmpldir,
    ), 'Create add command';
    ok $add->_add('sqitch_change_test', $out, $tmpl, 'sqlite', 'add'),
        'Write out a script';
    file_exists_ok $out;
    file_contents_is $out, <<EOF, 'The template should have been evaluated';
-- Deploy add:sqitch_change_test to sqlite

BEGIN;

-- XXX Add DDLs here.

COMMIT;
EOF
    is_deeply +MockOutput->get_info, [[__x 'Created {file}', file => $out ]],
        'Info should show $out created';
    unlink $out;

    # Try with requires and conflicts.
    ok $add =  $CLASS->new(
        sqitch    => $sqitch,
        requires  => [qw(foo bar)],
        conflicts => ['baz'],
        template_directory => $tmpldir,
    ), 'Create add cmd with requires and conflicts';

    $out = file 'test-add', 'another_change_test.sql';
    ok $add->_add('another_change_test', $out, $tmpl, 'sqlite', 'add'),
        'Write out a script with requires and conflicts';
    is_deeply +MockOutput->get_info, [[__x 'Created {file}', file => $out ]],
        'Info should show $out created';
    file_contents_is $out, <<EOF, 'The template should have been evaluated with requires and conflicts';
-- Deploy add:another_change_test to sqlite
-- requires: foo
-- requires: bar
-- conflicts: baz

BEGIN;

-- XXX Add DDLs here.

COMMIT;
EOF
    unlink $out;

  # Test with file name having a double extension
  $out = file 'test-add', 'duplicate_extension_test.sql.sql';
  $add->_add('duplicate_extension_test.sql', $out, $tmpl, 'sqlite', 'add');
  is_deeply +MockOutput->get_info, [[__x 'Created {file}', file => $out ]],
      'Info should show $out created';
  is_deeply +MockOutput->get_warn, [[__x(
      'File {file} has a double extension of {ext}',
      file => $out,
      ext => 'sql',
  )]], 'Should have warned about double extension';
  unlink $out;
};

t/add.t  view on Meta::CPAN

        $_ = 'die "NO ONE HERE";';
        return $i = !$i;
    }, 1;
};

$test_add->('Template::Tiny');

# Test _add() with Template.
shift @INC;
delete $INC{'Template.pm'};
SKIP: {
    skip 'Template Toolkit not installed', 16 unless eval 'use Template; 1';
    $test_add->('Template Toolkit');

    # Template Toolkit should throw an error on template syntax errors.
    ok my $add = $CLASS->new(sqitch => $sqitch, template_directory => $tmpldir),
        'Create add command';
    my $mock_add = Test::MockModule->new($CLASS);
    $mock_add->mock(_slurp => sub { \'[% IF foo %]' });
    my $out = file 'test-add', 'sqitch_change_test.sql';

    throws_ok { $add->_add('sqitch_change_test', $out, $tmpl) }
        'App::Sqitch::X', 'Should get an exception on TT syntax error';
    is $@->ident, 'add', 'TT exception ident should be "add"';
    is $@->message, __x(
        'Error executing {template}: {error}',
        template => $tmpl,
        error    => 'file error - parse error - input text line 1: unexpected end of input',
    ), 'TT exception message should include the original error message';
}

##############################################################################
# Test execute.
ok $add = $CLASS->new(
    sqitch => $sqitch,
    template_directory => $tmpldir,
), 'Create another add with template_directory';

# Override request_note().
my $change_mocker = Test::MockModule->new('App::Sqitch::Plan::Change');
my %request_params;
$change_mocker->mock(request_note => sub {
    my $self = shift;
    %request_params = @_;
    return $self->note;
});

# Set up a function to force the reload of the plan.
my $reload = sub {
    my $plan = shift;
    $plan->_plan( $plan->load);
    delete $plan->{$_} for qw(_changes _lines project uri);
};

my $deploy_file = file qw(test-add deploy widgets_table.sql);
my $revert_file = file qw(test-add revert widgets_table.sql);
my $verify_file = file qw(test-add verify widgets_table.sql);

my $plan = $add->default_target->plan;
is $plan->get('widgets_table'), undef, 'Should not have "widgets_table" in plan';
dir_not_exists_ok +File::Spec->catdir('test-add', $_) for qw(deploy revert verify);
ok $add->execute('widgets_table'), 'Add change "widgets_table"';

# Reload the plan.
$reload->($plan);

# Make sure the change was written to the plan file.
isa_ok my $change = $plan->get('widgets_table'), 'App::Sqitch::Plan::Change',
    'Added change';
is $change->name, 'widgets_table', 'Change name should be set';
is_deeply [$change->requires],  [], 'It should have no requires';
is_deeply [$change->conflicts], [], 'It should have no conflicts';
is_deeply \%request_params, {
    for => __ 'add',
    scripts => [$change->deploy_file, $change->revert_file, $change->verify_file],
}, 'It should have prompted for a note';

file_exists_ok $_ for ($deploy_file, $revert_file, $verify_file);
file_contents_like $deploy_file, qr/^-- Deploy add:widgets_table/,
    'Deploy script should look right';
file_contents_like $revert_file, qr/^-- Revert add:widgets_table/,
    'Revert script should look right';
file_contents_like $verify_file, qr/^-- Verify add:widgets_table/,
    'Verify script should look right';
is_deeply +MockOutput->get_info, [
    [__x 'Created {file}', file => $deploy_file],
    [__x 'Created {file}', file => $revert_file],
    [__x 'Created {file}', file => $verify_file],
    [__x 'Added "{change}" to {file}',
        change => 'widgets_table',
        file   => $target->plan_file,
    ],
], 'Info should have reported file creation';

# Make sure conflicts are avoided and conflicts and requires are respected.
ok $add = $CLASS->new(
    change_name        => 'foo_table',
    sqitch             => $sqitch,
    requires           => ['widgets_table'],
    conflicts          => [qw(dr_evil joker)],
    note               => [qw(hello there)],
    with_scripts       => { verify => 0 },
    template_directory => $tmpldir,
), 'Create another add with template_directory and no verify script';

$deploy_file = file qw(test-add deploy foo_table.sql);
$revert_file = file qw(test-add revert foo_table.sql);
$verify_file = file qw(test-add ferify foo_table.sql);
$deploy_file->touch;

file_exists_ok $deploy_file;
file_not_exists_ok $_ for ($revert_file, $verify_file);
is $plan->get('foo_table'), undef, 'Should not have "foo_table" in plan';
ok $add->execute, 'Add change "foo_table"';
file_exists_ok $_ for ($deploy_file, $revert_file);
file_not_exists_ok $verify_file;
$plan = $add->default_target->plan;
isa_ok $change = $plan->get('foo_table'), 'App::Sqitch::Plan::Change',
    '"foo_table" change';
is_deeply \%request_params, {
    for => __ 'add',
    scripts => [$change->deploy_file, $change->revert_file],
}, 'It should have prompted for a note';

is $change->name, 'foo_table', 'Change name should be set to "foo_table"';
is_deeply [$change->requires],  [dep 0, 'widgets_table'], 'It should have requires';
is_deeply [$change->conflicts], [map { dep 1, $_ } qw(dr_evil joker)], 'It should have conflicts';
is        $change->note, "hello\n\nthere", 'It should have a comment';

is_deeply +MockOutput->get_info, [
    [__x 'Skipped {file}: already exists', file => $deploy_file],
    [__x 'Created {file}', file => $revert_file],
    [__x 'Added "{change}" to {file}',
        change => 'foo_table [widgets_table !dr_evil !joker]',
        file   => $target->plan_file,
    ],
], 'Info should report skipping file and include dependencies';

# Make sure we die on an unknown argument.
throws_ok { $add->execute(qw(foo bar)) } 'App::Sqitch::X',
    'Should get an error on unkonwn argument';
is $@->ident, 'add', 'Unkown argument error ident should be "add"';
is $@->message, __nx(
    'Unknown argument "{arg}"',
    'Unknown arguments: {arg}',
    2,
    arg => 'foo, bar',
), 'Unknown argument error message should be correct';

# Make sure we die if the passed name conflicts with a target.
TARGET: {
    my $mock_add = Test::MockModule->new($CLASS);
    $mock_add->mock(parse_args => sub {
        return undef, [$target];
    });
    $mock_add->mock(name => 'blog');
    my $mock_target = Test::MockModule->new('App::Sqitch::Target');
    $mock_target->mock(name => 'blog');

    throws_ok { $add->execute('blog') } 'App::Sqitch::X',
        'Should get an error for conflict with target name';
    is $@->ident, 'add', 'Conflicting target error ident should be "add"';
    is $@->message, __x(
        'Name "{name}" identifies a target; use "--change {name}" to use it for the change name',
        name => 'blog',
    ), 'Conflicting target error message should be correct';
}

# Make sure we get a usage message when no name specified.
USAGE: {
    my @args;
    my $mock_add = Test::MockModule->new($CLASS);
    $mock_add->mock(usage => sub { @args = @_; die 'USAGE' });
    my $add = $CLASS->new(sqitch => $sqitch);
    throws_ok { $add->execute } qr/USAGE/,
        'No name arg or option should yield usage';

t/add.t  view on Meta::CPAN

    my $plan = $add->default_target->plan;
    is $plan->get('open_editor'), undef, 'Should not have "open_editor" in plan';
    ok $add->execute('open_editor'), 'Add change "open_editor"';

    # Instantiate fresh target and plan to force the file to be re-read.
    $target = App::Sqitch::Target->new(sqitch => $sqitch);
    $plan = App::Sqitch::Plan->new( sqitch => $sqitch, target => $target );

    isa_ok my $change = $plan->get('open_editor'), 'App::Sqitch::Plan::Change',
        'Added change';
    is $change->name, 'open_editor', 'Change name should be set';
    is $shell_cmd, join(' ', $sqitch->editor, $deploy_file, $revert_file, $verify_file),
        'It should have prompted to edit sql files';

    file_exists_ok $_ for ($deploy_file, $revert_file, $verify_file);
    file_contents_like +File::Spec->catfile(qw(test-add deploy open_editor.sql)),
        qr/^-- Deploy add:open_editor/, 'Deploy script should look right';
    file_contents_like +File::Spec->catfile(qw(test-add revert open_editor.sql)),
        qr/^-- Revert add:open_editor/, 'Revert script should look right';
    file_contents_like +File::Spec->catfile(qw(test-add verify open_editor.sql)),
        qr/^-- Verify add:open_editor/, 'Verify script should look right';
    is_deeply +MockOutput->get_info, [
        [__x 'Created {file}', file => $deploy_file],
        [__x 'Created {file}', file => $revert_file],
        [__x 'Created {file}', file => $verify_file],
        [__x 'Added "{change}" to {file}',
            change => 'open_editor',
            file   => $target->plan_file,
        ],
    ], 'Info should have reported file creation';
};

# Make sure an additional script and an exclusion work properly.
EXTRAS: {
    ok my $add = $CLASS->new(
        sqitch              => $sqitch,
        template_directory  => $tmpldir,
        with_scripts        => { verify => 0 },
        templates           => { whatev => file(qw(etc templates verify mysql.tmpl)) },
        note                => ['Testing custom scripts'],
    ), 'Create another add with custom script and no verify';

    my $deploy_file = file qw(test-add deploy custom_script.sql);
    my $revert_file = file qw(test-add revert custom_script.sql);
    my $verify_file = file qw(test-add verify custom_script.sql);
    my $whatev_file = file qw(test-add whatev custom_script.sql);

    ok $add->execute('custom_script'), 'Add change "custom_script"';
    my $plan = $add->default_target->plan;
    isa_ok my $change = $plan->get('custom_script'), 'App::Sqitch::Plan::Change',
        'Added change';
    is $change->name, 'custom_script', 'Change name should be set';
    is_deeply [$change->requires],  [], 'It should have no requires';
    is_deeply [$change->conflicts], [], 'It should have no conflicts';
    is_deeply \%request_params, {
        for => __ 'add',
        scripts => [ map { $change->script_file($_) } qw(deploy revert whatev)]
    }, 'It should have prompted for a note';

    file_exists_ok $_ for ($deploy_file, $revert_file, $whatev_file);
    file_not_exists_ok $verify_file;
    file_contents_like $deploy_file, qr/^-- Deploy add:custom_script/,
        'Deploy script should look right';
    file_contents_like $revert_file, qr/^-- Revert add:custom_script/,
        'Revert script should look right';
    file_contents_like $whatev_file, qr/^-- Verify add:custom_script/,
        'Whatev script should look right';
    file_contents_unlike $whatev_file, qr/^BEGIN/,
        'Whatev script should be based on the MySQL verify script';
    is_deeply +MockOutput->get_info, [
        [__x 'Created {file}', file => $deploy_file],
        [__x 'Created {file}', file => $revert_file],
        [__x 'Created {file}', file => $whatev_file],
        [__x 'Added "{change}" to {file}',
           change => 'custom_script',
           file   => $target->plan_file,
        ],
    ], 'Info should have reported file creation';

    # Relod the plan file to make sure change is written to it.
    $reload->($plan);
    isa_ok $change = $plan->get('custom_script'), 'App::Sqitch::Plan::Change',
        'Added change in reloaded plan';
}

# Make sure a configuration with multiple plans works.
MULTIPLAN: {
    make_path 'test-multiadd';
    END { remove_tree 'test-multiadd' };
    chdir 'test-multiadd';
    my $config = TestConfig->new(
        'core.engine'           => 'pg',
        'engine.pg.top_dir'     => 'pg',
        'engine.sqlite.top_dir' => 'sqlite',
        'engine.mysql.top_dir'  => 'mysql',
    );

    # Create plan files and determine the scripts that to be created.
    my @scripts = map {
        my $dir = dir $_;
        $dir->mkpath;
        $dir->file('sqitch.plan')->spew("%project=add\n\n");
        map { $dir->file($_, 'widgets.sql') } qw(deploy revert verify);
    } qw(pg sqlite mysql);

    # Load up the configuration for this project.
    my $sqitch = App::Sqitch->new(config => $config);
    ok my $add = $CLASS->new(
        sqitch             => $sqitch,
        note               => ['Testing multiple plans'],
        all                => 1,
        template_directory => dir->parent->subdir(qw(etc templates))
    ), 'Create another add with custom multiplan config';

    my @targets = App::Sqitch::Target->all_targets(sqitch => $sqitch);
    is @targets, 3, 'Should have three targets';

    # Make sure the target list matches our script list order (by engine).
    # pg always comes first, as primary engine, but the other two are random.
    push @targets, splice @targets, 1, 1 if $targets[1]->engine_key ne 'sqlite';

t/add.t  view on Meta::CPAN

         for @targets;
    file_exists_ok $_ for @scripts;

    # Make sure we see the proper output.
    my $info = MockOutput->get_info;
    my $ekey = $targets[1]->engine_key;
    if ($info->[4][0] !~ /$ekey/) {
        # Got the targets in a different order. So reorder results to match.
        push @{ $info } => splice @{ $info }, 4, 4;
    }
    is_deeply $info, [
        (map { [__x 'Created {file}', file => $_] } @scripts[0..2]),
        [
            __x 'Added "{change}" to {file}',
            change => 'widgets',
            file   => $targets[0]->plan_file,
        ],
        (map { [__x 'Created {file}', file => $_] } @scripts[3..5]),
        [
            __x 'Added "{change}" to {file}',
            change => 'widgets',
            file   => $targets[1]->plan_file,
        ],
        (map { [__x 'Created {file}', file => $_] } @scripts[6..8]),
        [
            __x 'Added "{change}" to {file}',
            change => 'widgets',
            file   => $targets[2]->plan_file,
        ],
    ], 'Info should have reported all script creations and plan updates';

    # Make sure we get an error using --all and a target arg.
    throws_ok { $add->execute('foo', 'pg' ) } 'App::Sqitch::X',
        'Should get an error for --all and a target arg';
    is $@->ident, 'add', 'Mixed arguments error ident should be "add"';
    is $@->message, __(
        'Cannot specify both --all and engine, target, or plan arugments'
    ), 'Mixed arguments error message should be correct';

    # Now try adding a change to just one engine. Remove --all
    ok $add = $CLASS->new(
        sqitch             => $sqitch,
        note               => ['Testing multiple plans'],
        template_directory => dir->parent->subdir(qw(etc templates))
    ), 'Create yet another add with custom multiplan config';

    ok $add->execute('choc', 'sqlite'), 'Add change "choc" to the sqlite plan';
    my %targets = map { $_->engine_key => $_ }
        App::Sqitch::Target->all_targets(sqitch => $sqitch);
    is keys %targets, 3, 'Should still have three targets';
    ok !$targets{pg}->plan->get('choc'), 'Should not have "choc" in the pg plan';
    ok !$targets{mysql}->plan->get('choc'), 'Should not have "choc" in the mysql plan';
    ok $targets{sqlite}->plan->get('choc'), 'Should have "choc" in the sqlite plan';

    @scripts = map {
        my $dir = dir $_;
        $dir->mkpath;
        map { $dir->file($_, 'choc.sql') } qw(deploy revert verify);
    } qw(sqlite pg mysql);
    file_exists_ok $_ for @scripts[0..2];
    file_not_exists_ok $_ for @scripts[3..8];
    is_deeply +MockOutput->get_info, [
        (map { [__x 'Created {file}', file => $_] } @scripts[0..2]),
        [
            __x 'Added "{change}" to {file}',
            change => 'choc',
            file   => $targets{sqlite}->plan_file,
        ],
    ], 'Info should have reported sqlite choc script creations and plan updates';

    chdir File::Spec->updir;
}

# Make sure we update only one plan but write out multiple target files.
MULTITARGET: {
    remove_tree 'test-multiadd';
    make_path 'test-multiadd';
    chdir 'test-multiadd';
    my $config = TestConfig->new(
        'core.engine'           => 'pg',
        'core.plan_file'        => 'sqitch.plan',
        'engine.pg.top_dir'     => 'pg',
        'engine.sqlite.top_dir' => 'sqlite',
        'add.all'               => 1,
    );
    file('sqitch.plan')->spew("%project=add\n\n");

    # Create list of scripts to be created.
    my @scripts = map {
        my $dir = dir $_;
        $dir->mkpath;
        map { $dir->file($_, 'widgets.sql') } qw(deploy revert verify);
    } qw(pg sqlite);

    # Load up the configuration for this project.
    my $sqitch = App::Sqitch->new(config => $config);
    ok my $add = $CLASS->new(
        sqitch             => $sqitch,
        note               => ['Testing multiple targets'],
        template_directory => dir->parent->subdir(qw(etc templates))
    ), 'Create another add with single plan, multi-target config';

    my @targets = App::Sqitch::Target->all_targets(sqitch => $sqitch);
    is @targets, 2, 'Should have two targets';
    is $targets[0]->plan_file, $targets[1]->plan_file,
        'Targets should use the same plan file';

    # Let's do this thing!
    ok $add->execute('widgets'), 'Add change "widgets" to all plans';
    ok $targets[0]->plan->get('widgets'), 'Should have "widgets" in the plan';
    file_exists_ok $_ for @scripts;

    is_deeply \%request_params, {
        for => __ 'add',
        scripts => \@scripts,
    }, 'Should have the proper files listed in the note promt';

    is_deeply +MockOutput->get_info, [
        (map { [__x 'Created {file}', file => $_] } @scripts),
        [
            __x 'Added "{change}" to {file}',



( run in 2.127 seconds using v1.01-cache-2.11-cpan-97f6503c9c8 )