#!/usr/bin/perl -w
use
Test::File
qw(file_not_exists_ok file_exists_ok)
;
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@
note|n|m=s@
all|a!
template-name|template|t=s
template-directory=s
with=s@
without=s@
use=s%
open-editor|edit|e!
plan-file|f=s
top-dir=s
)
],
'Options should be set up'
;
warning_is {
Getopt::Long::Configure(
qw(bundling pass_through)
);
ok Getopt::Long::GetOptionsFromArray(
[], {}, App::Sqitch->_core_opts,
$CLASS
->options,
),
'Should parse options'
;
}
undef
,
'Options should not conflict with core options'
;
sub
contents_of ($) {
my
$file
=
shift
;
open
my
$fh
,
"<:utf8_strict"
,
$file
or
die
"cannot open $file: $!"
;
local
$/;
return
<
$fh
>;
}
is_deeply
$CLASS
->configure(
$config
, {},
$sqitch
), {
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
},
'Should have default configuration with no config or opts'
;
is_deeply
$CLASS
->configure(
$config
, {
requires
=> [
qw(foo bar)
],
conflicts
=> [
'baz'
],
note
=> [
qw(hellow there)
],
}), {
requires
=> [
qw(foo bar)
],
conflicts
=> [
'baz'
],
note
=> [
qw(hellow there)
],
_cx
=> [],
},
'Should have get requires and conflicts options'
;
is_deeply
$CLASS
->configure(
$config
, {
template_directory
=>
't'
}), {
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
template_directory
=> dir(
't'
),
},
'Should set up template directory option'
;
is_deeply
$CLASS
->configure(
$config
, {
change_name
=>
'blog'
}), {
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
change_name
=>
'blog'
,
},
'Should set up change name option'
;
throws_ok {
$CLASS
->configure(
$config
, {
template_directory
=>
'__nonexistent__'
});
}
'App::Sqitch::X'
,
'Should die if --template-directory does not exist'
;
is $@->ident,
'add'
,
'Missing directory ident should be "add"'
;
is $@->message, __x(
'Directory "{dir}" does not exist'
,
dir
=>
'__nonexistent__'
,
),
'Missing directory error message should be correct'
;
throws_ok {
$CLASS
->configure(
$config
, {
template_directory
=>
'README.md'
});
}
'App::Sqitch::X'
,
'Should die if --template-directory does is not a dir'
;
is $@->ident,
'add'
,
'In alid directory ident should be "add"'
;
is $@->message, __x(
'"{dir}" is not a directory'
,
dir
=>
'README.md'
,
),
'Invalid directory error message should be correct'
;
is_deeply
$CLASS
->configure(
$config
, {
template_name
=>
'foo'
}), {
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
template_name
=>
'foo'
,
},
'Should set up template name option'
;
is_deeply
$CLASS
->configure(
$config
, {
all
=> 1,
with_scripts
=> {
deploy
=> 1,
revert
=> 1,
verify
=> 0 },
use
=> {
deploy
=>
'etc/templates/deploy/pg.tmpl'
,
revert
=>
'etc/templates/revert/pg.tmpl'
,
verify
=>
'etc/templates/verify/pg.tmpl'
,
whatev
=>
'etc/templates/verify/pg.tmpl'
,
},
}), {
all
=> 1,
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
with_scripts
=> {
deploy
=> 1,
revert
=> 1,
verify
=> 0 },
templates
=> {
deploy
=> file(
'etc/templates/deploy/pg.tmpl'
),
revert
=> file(
'etc/templates/revert/pg.tmpl'
),
verify
=> file(
'etc/templates/verify/pg.tmpl'
),
whatev
=> file(
'etc/templates/verify/pg.tmpl'
),
}
},
'Should have get template options'
;
CONFIG: {
my
$config
= TestConfig->from(
local
=> File::Spec->catfile(
qw(t add_change.conf)
)
);
my
$dir
= dir
't'
;
is_deeply
$CLASS
->configure(
$config
, {}), {
template_directory
=>
$dir
,
template_name
=>
'hi'
,
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
},
'Variables should by default not be loaded from config'
;
is_deeply
$CLASS
->configure(
$config
, {
set
=> {
yo
=>
'dawg'
}}), {
template_directory
=>
$dir
,
template_name
=>
'hi'
,
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
variables
=> {
foo
=>
'bar'
,
baz
=> [
qw(hi there you)
],
yo
=>
'dawg'
,
},
},
'--set should be merged with config variables'
;
is_deeply
$CLASS
->configure(
$config
, {
set
=> {
foo
=>
'ick'
}}), {
template_directory
=>
$dir
,
template_name
=>
'hi'
,
requires
=> [],
conflicts
=> [],
note
=> [],
_cx
=> [],
variables
=> {
foo
=>
'ick'
,
baz
=> [
qw(hi there you)
],
},
},
'--set should be override config variables'
;
}
is_deeply
$add
->requires, [],
'Requires should be an arrayref'
;
is_deeply
$add
->conflicts, [],
'Conflicts should be an arrayref'
;
is_deeply
$add
->note, [],
'Notes should be an arrayref'
;
is_deeply
$add
->variables, {},
'Varibles should be a hashref'
;
is
$add
->template_directory,
undef
,
'Default dir should be undef'
;
is
$add
->template_name,
undef
,
'Default temlate_name should be undef'
;
is_deeply
$add
->with_scripts, {
map
{
$_
=> 1}
qw(deploy revert verify)
},
'Default with_scripts should be all true'
;
is_deeply
$add
->templates, {},
'Default templates should be empty'
;
isa_ok
my
$check
=
$CLASS
->can(
'_check_script'
),
'CODE'
,
'_check_script'
;
my
$tmpl
=
'etc/templates/verify/pg.tmpl'
;
is
$check
->(
$tmpl
), file(
$tmpl
),
'_check_script should be okay with script'
;
throws_ok {
$check
->(
'nonexistent'
) }
'App::Sqitch::X'
,
'_check_script should die on nonexistent file'
;
is $@->ident,
'add'
,
'Nonexistent file ident should be "add"'
;
is $@->message, __x(
'Template {template} does not exist'
,
template
=>
'nonexistent'
,
),
'Nonexistent file error message should be correct'
;
throws_ok {
$check
->(
'lib'
) }
'App::Sqitch::X'
,
'_check_script should die on directory'
;
is $@->ident,
'add'
,
'Directory error ident should be "add"'
;
is $@->message, __x(
'Template {template} is not a file'
,
template
=>
'lib'
,
),
'Directory error message should be correct'
;
READCONFIG: {
my
$config
= TestConfig->from(
local
=> file(
't/templates.conf'
)->stringify
);
$config
->update(
'core.top_dir'
=> dir(
'test-add'
)->stringify);
ok
my
$sqitch
= App::Sqitch->new(
config
=>
$config
),
'Load another sqitch sqitch object'
;
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
),
'Create add with template config'
;
is_deeply
$add
->_config_templates(
$config
), {
deploy
=> file(
'etc/templates/deploy/pg.tmpl'
),
revert
=> file(
'etc/templates/revert/pg.tmpl'
),
test
=> file(
'etc/templates/verify/pg.tmpl'
),
verify
=> file(
'etc/templates/verify/pg.tmpl'
),
},
'Should load the config templates'
;
}
my
$tmpldir
= dir
'etc/templates'
;
my
$sysdir
= dir
'nonexistent'
;
my
$usrdir
= dir
'nonexistent'
;
my
$mock
= TestConfig->mock(
system_dir
=>
sub
{
$sysdir
},
user_dir
=>
sub
{
$usrdir
},
);
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_directory
=>
$tmpldir
),
'Add object with template directory'
;
is
$add
->template_name,
undef
,
'Template name should be undef'
;
my
$tname
=
$add
->template_name ||
$target
->engine_key;
is_deeply
$add
->all_templates(
$tname
), {
deploy
=> file(
'etc/templates/deploy/pg.tmpl'
),
revert
=> file(
'etc/templates/revert/pg.tmpl'
),
verify
=> file(
'etc/templates/verify/pg.tmpl'
),
},
'Should find all pg templates in directory'
;
is_deeply
$add
->all_templates(
'sqlite'
), {
deploy
=> file(
'etc/templates/deploy/sqlite.tmpl'
),
revert
=> file(
'etc/templates/revert/sqlite.tmpl'
),
verify
=> file(
'etc/templates/verify/sqlite.tmpl'
),
},
'Should find all sqlite templates in directory'
;
$usrdir
= dir
'etc'
;
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_name
=>
'sqlite'
),
'Add object with template name'
;
is_deeply
$add
->all_templates(
$add
->template_name), {
deploy
=> file(
'etc/templates/deploy/sqlite.tmpl'
),
revert
=> file(
'etc/templates/revert/sqlite.tmpl'
),
verify
=> file(
'etc/templates/verify/sqlite.tmpl'
),
},
'Should find all templates in user directory'
;
(
$usrdir
,
$sysdir
) = (
$sysdir
,
$usrdir
);
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_name
=>
'mysql'
),
'Add object with another template name'
;
is_deeply
$add
->all_templates(
$add
->template_name), {
deploy
=> file(
'etc/templates/deploy/mysql.tmpl'
),
revert
=> file(
'etc/templates/revert/mysql.tmpl'
),
verify
=> file(
'etc/templates/verify/mysql.tmpl'
),
},
'Should find all templates in systsem directory'
;
my
$tmp_dir
= dir tempdir
CLEANUP
=> 1;
for
my
$script
(
qw(deploy whatev)
) {
my
$subdir
=
$tmp_dir
->subdir(
$script
);
$subdir
->mkpath;
$subdir
->file(
'pg.tmpl'
)->touch;
}
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_directory
=>
$tmp_dir
),
'Add object with temporary template directory'
;
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'
;
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'
;
$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"
;
}
$tmpl
= file(
qw(etc templates deploy pg.tmpl)
);
is $ {
$add
->_slurp(
$tmpl
)}, contents_of
$tmpl
,
'_slurp() should load a reference to file contents'
;
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
;
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
;
$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
;
};
unshift
@INC
=>
sub
{
my
(
$self
,
$file
) =
@_
;
return
if
$file
ne
'Template.pm'
;
my
$i
= 0;
return
sub
{
$_
=
'die "NO ONE HERE";'
;
return
$i
= !
$i
;
}, 1;
};
$test_add
->(
'Template::Tiny'
);
shift
@INC
;
delete
$INC
{
'Template.pm'
};
SKIP: {
skip
'Template Toolkit not installed'
, 16
unless
eval
'use Template; 1'
;
$test_add
->(
'Template Toolkit'
);
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'
;
}
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_directory
=>
$tmpldir
,
),
'Create another add with template_directory'
;
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;
});
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
->(
$plan
);
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'
;
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'
;
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'
;
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'
;
}
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'
;
is_deeply \
@args
, [
$add
],
'No args should be passed to usage'
;
@args
= ();
throws_ok {
$add
->execute(
'pg'
) }
qr/USAGE/
,
'No name arg or option should yield usage'
;
is_deeply \
@args
, [
$add
],
'No args should be passed to usage'
;
@args
= ();
$add
=
$CLASS
->new(
sqitch
=> App::Sqitch->new(
config
=> TestConfig->new));
throws_ok {
$add
->execute }
qr/USAGE/
,
'No name arg or option should yield usage'
;
is_deeply \
@args
, [
$add
],
'No args should be passed to usage'
;
}
MOCKSHELL: {
my
$sqitch_mocker
= Test::MockModule->new(
'App::Sqitch'
);
my
$shell_cmd
;
$sqitch_mocker
->mock(
shell
=>
sub
{
$shell_cmd
=
$_
[1] });
$sqitch_mocker
->mock(
quote_shell
=>
sub
{
shift
;
join
' '
=>
@_
});
ok
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
template_directory
=>
$tmpldir
,
note
=> [
'Testing --open-editor'
],
open_editor
=> 1,
),
'Create another add with open_editor'
;
my
$deploy_file
= file
qw(test-add deploy open_editor.sql)
;
my
$revert_file
= file
qw(test-add revert open_editor.sql)
;
my
$verify_file
= file
qw(test-add verify open_editor.sql)
;
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"'
;
$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'
;
};
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'
;
$reload
->(
$plan
);
isa_ok
$change
=
$plan
->get(
'custom_script'
),
'App::Sqitch::Plan::Change'
,
'Added change in reloaded plan'
;
}
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'
,
);
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)
;
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'
;
push
@targets
,
splice
@targets
, 1, 1
if
$targets
[1]->engine_key ne
'sqlite'
;
ok
$add
->execute(
'widgets'
),
'Add change "widgets" to all plans'
;
ok
$_
->plan->get(
'widgets'
),
'Should have "widgets" in '
.
$_
->engine_key .
' plan'
for
@targets
;
file_exists_ok
$_
for
@scripts
;
my
$info
= MockOutput->get_info;
my
$ekey
=
$targets
[1]->engine_key;
if
(
$info
->[4][0] !~ /
$ekey
/) {
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'
;
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'
;
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;
}
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"
);
my
@scripts
=
map
{
my
$dir
= dir
$_
;
$dir
->mkpath;
map
{
$dir
->file(
$_
,
'widgets.sql'
) }
qw(deploy revert verify)
;
}
qw(pg sqlite)
;
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'
;
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}'
,
change
=>
'widgets'
,
file
=>
$targets
[0]->plan_file,
],
],
'Info should have reported all script creations and one plan update'
;
chdir
File::Spec->updir;
}
ONETOP: {
remove_tree
'test-multiadd'
;
make_path
'test-multiadd'
;
chdir
'test-multiadd'
;
my
$config
= TestConfig->new(
'core.engine'
=>
'pg'
,
'engine.pg.plan_file'
=>
'pg.plan'
,
'engine.sqlite.plan_file'
=>
'sqlite.plan'
,
);
file(
"$_.plan"
)->spew(
"%project=add\n\n"
)
for
qw(pg sqlite)
;
my
@scripts
=
map
{ file
$_
,
'widgets.sql'
}
qw(deploy revert verify)
;
my
$sqitch
= App::Sqitch->new(
config
=>
$config
);
ok
my
$add
=
$CLASS
->new(
sqitch
=>
$sqitch
,
note
=> [
'Testing two targets, one top_dir'
],
all
=> 1,
template_directory
=> dir->parent->subdir(
qw(etc templates)
)
),
'Create another add with two targets, one top dir'
;
my
@targets
= App::Sqitch::Target->all_targets(
sqitch
=>
$sqitch
);
is
@targets
, 2,
'Should have two targets'
;
is
$targets
[0]->plan_file, file(
'pg.plan'
),
'First target plan should be in pg.plan'
;
is
$targets
[1]->plan_file, file(
'sqlite.plan'
),
'Second target plan should be in sqlite.plan'
;
ok
$add
->execute(
'widgets'
),
'Add change "widgets" to all plans'
;
ok
$_
->plan->get(
'widgets'
),
'Should have "widgets" in '
.
$_
->engine_key .
' plan'
for
@targets
;
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
my
$info
= MockOutput->get_info, [
(
map
{ [__x
'Created {file}'
,
file
=>
$_
] }
@scripts
),
[
__x
'Added "{change}" to {file}'
,
change
=>
'widgets'
,
file
=>
$targets
[0]->plan_file,
],
(
map
{ [__x
'Skipped {file}: already exists'
,
file
=>
$_
] }
@scripts
),
[
__x
'Added "{change}" to {file}'
,
change
=>
'widgets'
,
file
=>
$targets
[1]->plan_file,
],
],
'Info should have script creations and skips'
;
chdir
File::Spec->updir;
}
can_ok
$CLASS
,
'options'
,
'_parse_opts'
;
ok
$add
=
$CLASS
->new({
sqitch
=>
$sqitch
}),
"Create a $CLASS object again"
;
is_deeply
$add
->_parse_opts([]),
{
with_scripts
=> {
map
{
$_
=> 1}
qw(deploy revert verify)
} },
'Base _parse_opts should return the script config'
;
is_deeply
$add
->_parse_opts([1]), {
with_scripts
=> {
deploy
=> 1,
verify
=> 1,
revert
=> 1 },
},
'_parse_opts() hould use options spec'
;
my
$args
= [
qw(
--note foo
--template bar
whatever
)
];
is_deeply
$add
->_parse_opts(
$args
), {
note
=> [
'foo'
],
template_name
=>
'bar'
,
with_scripts
=> {
deploy
=> 1,
verify
=> 1,
revert
=> 1 },
},
'_parse_opts() should parse options spec'
;
is_deeply
$args
, [
'whatever'
],
'Args array should be cleared of options'
;
push
@{
$args
},
'--set'
=>
'schema=foo'
,
'--set'
=>
'table=bar'
;
is_deeply
$add
->_parse_opts(
$args
), {
set
=> {
schema
=>
'foo'
,
table
=>
'bar'
},
with_scripts
=> {
deploy
=> 1,
verify
=> 1,
revert
=> 1 },
},
'_parse_opts() should parse --set options'
;
is_deeply
$args
, [
'whatever'
],
'Args array should be cleared of options'
;
push
@{
$args
},
'--set'
=>
'column=id'
,
'--set'
=>
'column=name'
;
is_deeply
$add
->_parse_opts(
$args
), {
set
=> {
column
=> [
qw(id name)
] },
with_scripts
=> {
deploy
=> 1,
verify
=> 1,
revert
=> 1 },
},
'_parse_opts() should parse --set options with repeting key'
;
is_deeply
$args
, [
'whatever'
],
'Args array should be cleared of options'
;
push
@{
$args
},
qw(--with deploy --without verify --use)
,
"foo=$tmpl"
;
is_deeply
$add
->_parse_opts(
$args
), {
with_scripts
=> {
deploy
=> 1,
verify
=> 0,
revert
=> 1 },
use
=> {
foo
=>
$tmpl
}
},
'_parse_opts() should parse --with, --without, and --user'
;
is_deeply
$args
, [
'whatever'
],
'Args array should be cleared of options'
;