Improve trigger 'scope' attribute support (RT#119997)
[dbsrgits/SQL-Translator.git] / lib / Test / SQL / Translator.pm
CommitLineData
9bbbf403 1package Test::SQL::Translator;
2
9bbbf403 3=pod
4
5=head1 NAME
6
7Test::SQL::Translator - Test::More test functions for the Schema objects.
8
9=cut
10
11use strict;
12use warnings;
11fee3e0 13use Test::More;
14use SQL::Translator::Schema::Constants;
9bbbf403 15
16use base qw(Exporter);
0c04c5a2 17our @EXPORT_OK;
18our $VERSION = '1.59';
19our @EXPORT = qw(
a0eb602d 20 schema_ok
9bbbf403 21 table_ok
22 field_ok
23 constraint_ok
24 index_ok
25 view_ok
26 trigger_ok
27 procedure_ok
49133ae7 28 maybe_plan
9bbbf403 29);
9bbbf403 30
9bbbf403 31# $ATTRIBUTES{ <schema_object_name> } = { <attribname> => <default>, ... }
11fee3e0 32my %ATTRIBUTES = (
33 field => {
34 name => undef,
35 data_type => '',
36 default_value => undef,
37 size => '0',
38 is_primary_key => 0,
39 is_unique => 0,
40 is_nullable => 1,
41 is_foreign_key => 0,
42 is_auto_increment => 0,
43 comments => '',
44 extra => {},
45 # foreign_key_reference,
46 is_valid => 1,
47 # order
48 },
49 constraint => {
50 name => '',
51 type => '',
52 deferrable => 1,
53 expression => '',
54 is_valid => 1,
55 fields => [],
56 match_type => '',
57 options => [],
58 on_delete => '',
59 on_update => '',
60 reference_fields => [],
61 reference_table => '',
62 extra => {},
63 },
64 index => {
65 fields => [],
66 is_valid => 1,
67 name => "",
68 options => [],
69 type => NORMAL,
70 extra => {},
71 },
72 view => {
73 name => "",
74 sql => "",
75 fields => [],
76 is_valid => 1,
77 extra => {},
78 },
79 trigger => {
80 name => '',
81 perform_action_when => undef,
82 database_events => undef,
83 on_table => undef,
84 action => undef,
85 is_valid => 1,
86 extra => {},
87 },
88 procedure => {
89 name => '',
90 sql => '',
91 parameters => [],
92 owner => '',
93 comments => '',
94 extra => {},
95 },
96 table => {
97 comments => undef,
98 name => '',
99 #primary_key => undef, # pkey constraint
100 options => [],
101 #order => 0,
102 fields => undef,
103 constraints => undef,
104 indices => undef,
105 is_valid => 1,
106 extra => {},
107 },
108 schema => {
109 name => '',
110 database => '',
111 procedures => undef, # [] when set
112 tables => undef, # [] when set
113 triggers => undef, # [] when set
114 views => undef, # [] when set
115 is_valid => 1,
116 extra => {},
117 }
118);
9bbbf403 119
120# Given a test hash and schema object name set any attribute keys not present in
121# the test hash to their default value for that schema object type.
122# e.g. default_attribs( $test, "field" );
123sub default_attribs {
11fee3e0 124 my ($hashref, $object_type) = @_;
125
126 if ( !exists $ATTRIBUTES{ $object_type } ) {
ea93df61 127 die "Can't add default attribs for unknown Schema "
11fee3e0 128 . "object type '$object_type'.";
129 }
130
ea93df61 131 for my $attr (
11fee3e0 132 grep { !exists $hashref->{ $_ } }
ea93df61 133 keys %{ $ATTRIBUTES{ $object_type } }
11fee3e0 134 ) {
135 $hashref->{ $attr } = $ATTRIBUTES{ $object_type }{ $attr }
136 }
137
138 return $hashref;
9bbbf403 139}
140
141# Format test name so it will prepend the test names used below.
142sub t_name {
143 my $name = shift;
144 $name ||= "";
145 $name = "$name - " if $name;
146 return $name;
147}
148
149sub field_ok {
150 my ($f1,$test,$name) = @_;
151 my $t_name = t_name($name);
152 default_attribs($test,"field");
153
154 unless ($f1) {
155 fail " Field '$test->{name}' doesn't exist!";
a0eb602d 156 # TODO Do a skip on the following tests. Currently the test counts wont
157 # match at the end. So at least it fails.
9bbbf403 158 return;
159 }
160
a0eb602d 161 my $full_name = $f1->table->name.".".$test->{name};
162
163 is( $f1->name, $test->{name}, "${t_name}Field '$full_name'" );
9bbbf403 164
165 is( $f1->is_valid, $test->{is_valid},
a0eb602d 166 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
9bbbf403 167
168 is( $f1->data_type, $test->{data_type},
169 "$t_name type is '$test->{data_type}'" );
170
171 is( $f1->size, $test->{size}, "$t_name size is '$test->{size}'" );
172
173 is( $f1->default_value, $test->{default_value},
174 "$t_name default value is "
175 .(defined($test->{default_value}) ? "'$test->{default_value}'" : "UNDEF" )
176 );
177
178 is( $f1->is_nullable, $test->{is_nullable},
179 "$t_name ".($test->{is_nullable} ? 'can' : 'cannot').' be null' );
180
181 is( $f1->is_unique, $test->{is_unique},
182 "$t_name ".($test->{is_unique} ? 'can' : 'cannot').' be unique' );
183
184 is( $f1->is_primary_key, $test->{is_primary_key},
185 "$t_name is ".($test->{is_primary_key} ? '' : 'not ').'a primary_key' );
186
187 is( $f1->is_foreign_key, $test->{is_foreign_key},
188 "$t_name is ".($test->{is_foreign_key} ? '' : 'not').' a foreign_key' );
189
190 is( $f1->is_auto_increment, $test->{is_auto_increment},
191 "$t_name is "
192 .($test->{is_auto_increment} ? '' : 'not ').'an auto_increment' );
193
194 is( $f1->comments, $test->{comments}, "$t_name comments" );
195
196 is_deeply( { $f1->extra }, $test->{extra}, "$t_name extra" );
197}
198
199sub constraint_ok {
200 my ($obj,$test,$name) = @_;
201 my $t_name = t_name($name);
202 default_attribs($test,"constraint");
203
204 is( $obj->name, $test->{name}, "${t_name}Constraint '$test->{name}'" );
205
206 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
207
208 is( $obj->deferrable, $test->{deferrable},
209 "$t_name ".($test->{deferrable} ? 'can' : 'cannot').' be deferred' );
210
211 is( $obj->is_valid, $test->{is_valid},
212 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
213
214 is($obj->table->name,$test->{table},"$t_name table is '$test->{table}'" );
215
216 is( $obj->expression, $test->{expression},
217 "$t_name expression is '$test->{expression}'" );
218
219 is_deeply( [$obj->fields], $test->{fields},
220 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
221
222 is( $obj->reference_table, $test->{reference_table},
223 "$t_name reference_table is '$test->{reference_table}'" );
224
225 is_deeply( [$obj->reference_fields], $test->{reference_fields},
226 "$t_name reference_fields are '".join(",",@{$test->{reference_fields}})."'" );
227
228 is( $obj->match_type, $test->{match_type},
229 "$t_name match_type is '$test->{match_type}'" );
230
231 is( $obj->on_delete, $test->{on_delete},
232 "$t_name on_delete is '$test->{on_delete}'" );
233
234 is( $obj->on_update, $test->{on_update},
235 "$t_name on_update is '$test->{on_update}'" );
236
237 is_deeply( [$obj->options], $test->{options},
238 "$t_name options are '".join(",",@{$test->{options}})."'" );
ea93df61 239
b1789409 240 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 241}
242
243sub index_ok {
244 my ($obj,$test,$name) = @_;
245 my $t_name = t_name($name);
246 default_attribs($test,"index");
247
248 is( $obj->name, $test->{name}, "${t_name}Index '$test->{name}'" );
249
250 is( $obj->is_valid, $test->{is_valid},
251 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
252
253 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
254
255 is_deeply( [$obj->fields], $test->{fields},
256 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
257
258 is_deeply( [$obj->options], $test->{options},
259 "$t_name options are '".join(",",@{$test->{options}})."'" );
ea93df61 260
b1789409 261 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 262}
263
264sub trigger_ok {
265 my ($obj,$test,$name) = @_;
266 my $t_name = t_name($name);
267 default_attribs($test,"index");
268
14ad260a 269 is( $obj->name, $test->{name}, "${t_name}Trigger '$test->{name}'" );
9bbbf403 270
271 is( $obj->is_valid, $test->{is_valid},
272 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
273
274 is( $obj->perform_action_when, $test->{perform_action_when},
275 "$t_name perform_action_when is '$test->{perform_action_when}'" );
276
11fee3e0 277 is( join(',', $obj->database_events), $test->{database_events},
278 sprintf("%s database_events is '%s'",
279 $t_name,
280 $test->{'database_events'},
281 )
282 );
9bbbf403 283
284 is( $obj->on_table, $test->{on_table},
285 "$t_name on_table is '$test->{on_table}'" );
286
c0ec0e22 287 is( $obj->scope, $test->{scope}, "$t_name scope is '$test->{scope}'" )
288 if exists $test->{scope};
289
9bbbf403 290 is( $obj->action, $test->{action}, "$t_name action is '$test->{action}'" );
ea93df61 291
b1789409 292 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 293}
294
295sub view_ok {
296 my ($obj,$test,$name) = @_;
297 my $t_name = t_name($name);
298 default_attribs($test,"index");
299
300 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
301
302 is( $obj->name, $test->{name}, "${t_name}View '$test->{name}'" );
303
304 is( $obj->is_valid, $test->{is_valid},
305 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
306
307 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
308
309 is_deeply( [$obj->fields], $test->{fields},
310 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
ea93df61 311
b1789409 312 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 313}
314
315sub procedure_ok {
316 my ($obj,$test,$name) = @_;
317 my $t_name = t_name($name);
318 default_attribs($test,"index");
319
320 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
321
322 is( $obj->name, $test->{name}, "${t_name}Procedure '$test->{name}'" );
323
324 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
325
326 is_deeply( [$obj->parameters], $test->{parameters},
327 "$t_name parameters are '".join(",",@{$test->{parameters}})."'" );
328
ea93df61 329 is( $obj->comments, $test->{comments},
9bbbf403 330 "$t_name comments is '$test->{comments}'" );
331
332 is( $obj->owner, $test->{owner}, "$t_name owner is '$test->{owner}'" );
ea93df61 333
b1789409 334 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 335}
336
337sub table_ok {
338 my ($obj,$test,$name) = @_;
339 my $t_name = t_name($name);
340 default_attribs($test,"table");
341 my %arg = %$test;
342
343 my $tbl_name = $arg{name} || die "Need a table name to test.";
a0eb602d 344 is( $obj->{name}, $arg{name}, "${t_name}Table '$arg{name}'" );
9bbbf403 345
346 is_deeply( [$obj->options], $test->{options},
347 "$t_name options are '".join(",",@{$test->{options}})."'" );
348
b1789409 349 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
350
9bbbf403 351 # Fields
352 if ( $arg{fields} ) {
a0eb602d 353 my @fldnames = map {$_->{name}} @{$arg{fields}};
ea93df61 354 is_deeply(
a0eb602d 355 [ map {$_->name} $obj->get_fields ],
356 [ @fldnames ],
357 "${t_name} field names are ".join(", ",@fldnames)
358 );
9bbbf403 359 foreach ( @{$arg{fields}} ) {
360 my $f_name = $_->{name} || die "Need a field name to test.";
a0eb602d 361 next unless my $fld = $obj->get_field($f_name);
362 field_ok( $fld, $_, $name );
9bbbf403 363 }
364 }
365 else {
366 is(scalar($obj->get_fields), undef,
a0eb602d 367 "${t_name} has no fields.");
9bbbf403 368 }
369
a0eb602d 370 # Constraints and Indices
371 _test_kids($obj, $test, $name, {
11fee3e0 372 constraint => 'constraints',
373 index => 'indices',
a0eb602d 374 });
375}
376
377sub _test_kids {
11fee3e0 378 my ( $obj, $test, $name, $kids ) = @_;
379 my $t_name = t_name($name);
a0eb602d 380 my $obj_name = ref $obj;
381 ($obj_name) = $obj_name =~ m/^.*::(.*)$/;
382
11fee3e0 383 while ( my ( $object_type, $plural ) = each %$kids ) {
384 next unless defined $test->{ $plural };
385
386 if ( my @tests = @{ $test->{ $plural } } ) {
9bbbf403 387 my $meth = "get_$plural";
11fee3e0 388 my @objects = $obj->$meth;
389 is( scalar(@objects), scalar(@tests),
ea93df61 390 "${t_name}$obj_name has " . scalar(@tests) . " $plural"
11fee3e0 391 );
392
393 for my $object (@objects) {
394 my $ans = { lc($obj_name) => $obj->name, %{ shift @tests } };
395
396 my $meth = "${object_type}_ok";
397 {
398 no strict 'refs';
399 $meth->( $object, $ans, $name );
9bbbf403 400 }
401 }
402 }
403 }
404}
405
406sub schema_ok {
407 my ($obj,$test,$name) = @_;
408 my $t_name = t_name($name);
409 default_attribs($test,"schema");
a0eb602d 410
411 is( $obj->name, $test->{name}, "${t_name}Schema name is '$test->{name}'" );
412
413 is( $obj->database, $test->{database},
414 "$t_name database is '$test->{database}'" );
ea93df61 415
b1789409 416 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
a0eb602d 417
418 is( $obj->is_valid, $test->{is_valid},
419 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
420
421 # Tables
422 if ( $test->{tables} ) {
423 is_deeply( [ map {$_->name} $obj->get_tables ],
424 [ map {$_->{name}} @{$test->{tables}} ],
425 "${t_name} table names match" );
426 foreach ( @{$test->{tables}} ) {
427 my $t_name = $_->{name} || die "Need a table name to test.";
428 table_ok( $obj->get_table($t_name), $_, $name );
429 }
430 }
431 else {
432 is(scalar($obj->get_tables), undef,
433 "${t_name} has no tables.");
434 }
435
436 # Procedures, Triggers, Views
437 _test_kids($obj, $test, $name, {
11fee3e0 438 procedure => 'procedures',
439 trigger => 'triggers',
440 view => 'views',
a0eb602d 441 });
9bbbf403 442}
443
49133ae7 444# maybe_plan($ntests, @modules)
445#
446# Calls plan $ntests if @modules can all be loaded; otherwise,
447# calls skip_all with an explanation of why the tests were skipped.
448sub maybe_plan {
449 my ($ntests, @modules) = @_;
450 my @errors;
451
452 for my $module (@modules) {
453 eval "use $module;";
559aa8a9 454 next if !$@;
455
456 if ($@ =~ /Can't locate (\S+)/) {
49133ae7 457 my $mod = $1;
458 $mod =~ s/\.pm$//;
459 $mod =~ s#/#::#g;
460 push @errors, $mod;
461 }
559aa8a9 462 elsif ($@ =~ /([\w\:]+ version [\d\.]+) required.+?this is only version/) {
463 push @errors, $1;
464 }
ac8d330a 465 elsif ($@ =~ /Can't load .+? for module .+?DynaLoader\.pm/i ) {
466 push @errors, $module;
467 }
49133ae7 468 }
469
470 if (@errors) {
471 my $msg = sprintf "Missing dependenc%s: %s",
472 @errors == 1 ? 'y' : 'ies',
473 join ", ", @errors;
474 plan skip_all => $msg;
475 }
0142de97 476 return unless defined $ntests;
477
478 if ($ntests ne 'no_plan') {
49133ae7 479 plan tests => $ntests;
480 }
dc34f950 481 else {
482 plan 'no_plan';
483 }
49133ae7 484}
485
9bbbf403 4861; # compile please ===========================================================
487__END__
488
489=pod
490
491=head1 SYNOPSIS
492
493 # t/magic.t
494
495 use FindBin '$Bin';
496 use Test::More;
497 use Test::SQL::Translator;
498
499 # Run parse
500 my $sqlt = SQL::Translator->new(
501 parser => "Magic",
502 filename => "$Bin/data/magic/test.magic",
e2a489c2 503 ...
9bbbf403 504 );
505 ...
506 my $schema = $sqlt->schema;
e2a489c2 507
9bbbf403 508 # Test the table it produced.
509 table_ok( $schema->get_table("Customer"), {
510 name => "Customer",
511 fields => [
512 {
513 name => "CustomerID",
514 data_type => "INT",
515 size => 12,
516 default_value => undef,
517 is_nullable => 0,
518 is_primary_key => 1,
519 },
520 {
521 name => "bar",
522 data_type => "VARCHAR",
523 size => 255,
524 is_nullable => 0,
525 },
526 ],
527 constraints => [
528 {
529 type => "PRIMARY KEY",
530 fields => "CustomerID",
531 },
532 ],
533 indices => [
534 {
535 name => "barindex",
536 fields => ["bar"],
537 },
538 ],
539 });
540
13eb146e 541=head1 DESCRIPTION
9bbbf403 542
e2a489c2 543Provides a set of Test::More tests for Schema objects. Testing a parsed
9bbbf403 544schema is then as easy as writing a perl data structure describing how you
9aabed4a 545expect the schema to look. Also provides C<maybe_plan> for conditionally running
e2a489c2 546tests based on their dependencies.
9bbbf403 547
e2a489c2 548The data structures given to the test subs don't have to include all the
9bbbf403 549possible values, only the ones you expect to have changed. Any left out will be
10f70490 550tested to make sure they are still at their default value. This is a useful
9bbbf403 551check that you your parser hasn't accidentally set schema values you didn't
e2a489c2 552expect it to.
9bbbf403 553
13eb146e 554For an example of the output run the F<t/16xml-parser.t> test.
9bbbf403 555
556=head1 Tests
557
e2a489c2 558All the tests take a first arg of the schema object to test, followed by a
9bbbf403 559hash ref describing how you expect that object to look (you only need give the
560attributes you expect to have changed from the default).
13eb146e 561The 3rd arg is an optional test name to prepend to all the generated test
9bbbf403 562names.
563
564=head2 table_ok
565
566=head2 field_ok
567
568=head2 constraint_ok
569
570=head2 index_ok
571
572=head2 view_ok
573
574=head2 trigger_ok
575
576=head2 procedure_ok
577
49133ae7 578=head1 CONDITIONAL TESTS
579
580The C<maybe_plan> function handles conditionally running an individual
581test. It is here to enable running the test suite even when dependencies
582are missing; not having (for example) GraphViz installed should not keep
583the test suite from passing.
584
585C<maybe_plan> takes the number of tests to (maybe) run, and a list of
586modules on which test execution depends:
587
588 maybe_plan(180, 'SQL::Translator::Parser::MySQL');
589
590If one of C<SQL::Translator::Parser::MySQL>'s dependencies does not exist,
591then the test will be skipped.
592
0142de97 593Instead of a number of tests, you can pass C<undef> if you're using
594C<done_testing()>, or C<'no_plan'> if you don't want a plan at all.
595
9bbbf403 596=head1 EXPORTS
597
49133ae7 598table_ok, field_ok, constraint_ok, index_ok, view_ok, trigger_ok, procedure_ok,
599maybe_plan
9bbbf403 600
601=head1 TODO
602
603=over 4
604
605=item Test the tests!
606
19458c2f 607=item Test Count Constants
608
9aabed4a 609Constants to give the number of tests each C<*_ok> sub uses. e.g. How many tests
610does C<field_ok> run? Can then use these to set up the test plan easily.
19458c2f 611
9bbbf403 612=item Test skipping
613
4b2d9113 614As the test subs wrap up lots of tests in one call you can't skip individual
9bbbf403 615tests only whole sets e.g. a whole table or field.
9aabed4a 616We could add C<skip_*> items to the test hashes to allow per test skips. e.g.
9bbbf403 617
618 skip_is_primary_key => "Need to fix primary key parsing.",
619
620=item yaml test specs
621
13eb146e 622Maybe have the test subs also accept yaml for the test hash ref as it is much
9bbbf403 623nicer for writing big data structures. We can then define tests as in input
624schema file and test yaml file to compare it against.
625
626=back
627
9bbbf403 628=head1 AUTHOR
629
ea93df61 630Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>,
11fee3e0 631Darren Chamberlain <darren@cpan.org>.
9bbbf403 632
11ad2df9 633Thanks to Ken Y. Clark for the original table and field test code taken from
634his mysql test.
9bbbf403 635
636=head1 SEE ALSO
637
638perl(1), SQL::Translator, SQL::Translator::Schema, Test::More.
639
640=cut