# vi:filetype= # Tests for the OpenResty::RestyScript module. (Action part) #use Smart::Comments; my $skip; my $ExePath; BEGIN { use FindBin; $ExePath = "$FindBin::Bin/../haskell/bin/restyscript"; if (!-f $ExePath) { $skip = "$ExePath is not found.\n"; return; } if (!-x $ExePath) { $skip = "$ExePath is not an executable.\n"; return; } }; use Test::Base $skip ? (skip_all => $skip) : (); use lib 'lib'; use OpenResty::RestyScript; #plan tests => 3 * blocks(); plan 'no_plan'; # For Action handler, we should use DBI's quote and quote_ident respectively. The version below is just for the testing purpose. sub quote { my $s = shift; if (!defined $s) { $s = '' } $s =~ s/\n/{NEW_LINE}/g; $s =~ s/\r/{RETURN}/g; $s =~ s/\t/{TAB}/g; '$y$' . $s . '$y$'; } sub quote_only { my $s = shift; if (!defined $s) { $s = '' } $s =~ s/\n/{NEW_LINE}/g; $s =~ s/\r/{RETURN}/g; $s =~ s/\t/{TAB}/g; $s } sub quote_ident { qq/"$_[0]"/ } run { my $block = shift; my $name = $block->name; my %in_vars; my $in_vars = $block->in_vars; if (defined $in_vars) { my @ln = split /\n+/, $in_vars; map { my ($var, $val) = split /=/, $_, 2; $in_vars{$var} = $val; } @ln; } my $sql = $block->sql or die "$name - No --- sql section found.\n"; my $view = OpenResty::RestyScript->new('action', $sql); my ($frags, $stats); eval { ($frags, $stats) = $view->compile; }; ### Fragments: $frags ### Stats: $stats my $res; if ($@ && !defined $block->error) { warn $@ } elsif (defined $block->error) { my $error = $block->error || ''; $error =~ s/^\s+$//g; (my $got = $@) =~ s/^expecting .*\n//ms; is $got, $error, "$name - error msg ok"; } #%in_vars, my (@models, @cols, @vars, @unbound); if ($stats) { @models = @{ $stats->{modelList} }; my @m; for my $model (@models) { if ($model =~ s/^\$//) { if (defined $in_vars{$model}) { push @m, $in_vars{$model}; } else { push @m, '$'.$model; } } else { push @m, $model; } } @models = @m; } ### @models my $ex_models = $block->models; if (defined $ex_models) { is join(' ', @models), $block->models, "$name - model list ok"; } # XXX FIX ME # for the real action handler, we need to do typechecking in a # separate pass. Below is merely a hack regarding type inferencing my @out_cmds; my $cmds = $frags; for my $cmd (@$cmds) { croak "Invalid command: ", Dumper($cmd) unless ref $cmd; if (@$cmd == 1 and ref $cmd->[0]) { # being a SQL command my $cmd = $cmd->[0]; ### $cmd my @bits; my %types; for my $frag (@$cmd) { if (ref $frag) { # being a variable my ($var, $type) = @$frag; if ($type eq 'symbol') { $types{$var} = 'symbol' } my $quote = $types{$var} ? \"e_ident : \"e; push @vars, $var; if (!defined $in_vars{$var}) { push @unbound, $var; push @bits, $quote->(''); } else { push @bits, $quote->($in_vars{$var}); } } else { push @bits, $frag; } } push @out_cmds, @bits ? (join '', @bits) : ''; } else { # being an HTTP command ### HTTP cmd: $cmd my ($http_meth, $url, $content) = @$cmd; if ($http_meth ne 'POST' and $http_meth ne 'PUT' and $content) { die "Content part not allowed for $http_meth\n"; } my @bits = "$http_meth "; for my $frag (@$url) { if (ref $frag) { # being a variable my ($var) = @$frag; # we ignore the type part here since it should always be "quoted" my $quote = \"e_only; push @vars, $var; if (!defined $in_vars{$var}) { push @unbound, $var; push @bits, $quote->(''); } else { push @bits, $quote->($in_vars{$var}); } } else { push @bits, $frag; } } push @bits, ' '; for my $frag (@$content) { if (ref $frag) { # being a variable my ($var, $type) = @$frag; # type could only be literal or quoted here my $quote = $type eq 'quoted' ? \"e_only : \"e_ident; push @vars, $var; if (!defined $in_vars{$var}) { push @unbound, $var; push @bits, $quote->(''); } else { push @bits, $quote->($in_vars{$var}); } } else { push @bits, $frag; } } push @out_cmds, @bits ? (join '', @bits) : ''; } } my $out = @out_cmds ? (join "\n", @out_cmds) : ''; $out =~ s/\s+$//gsm; $out =~ s/\n$//g; ### $out if (defined $block->out) { (my $expected = $block->out) =~ s/\n$//g; is $out, $expected, "$name - sql emittion ok"; } my $ex_vars = $block->vars; if (defined $ex_vars) { is join(' ', @vars), $ex_vars, "$name - var list ok"; } my $ex_unbound = $block->unbound; if (defined $ex_unbound) { is join(' ', @unbound), $ex_unbound, "$name - unbound var list ok"; } }; __DATA__ === TEST 1: Simple --- sql select * from Carrie; select * from Post --- error --- models: Carrie Post --- out select * from "Carrie" select * from "Post" === TEST 2: basic delete --- sql delete from $foo where $foo > $id; --- vars: foo foo id --- models: $foo --- out delete from "" where "" > $y$$y$ === TEST 3: basic delete (with vars) --- sql delete from $foo where $foo > $id; --- vars: foo foo id --- models: cat --- in_vars foo=cat id=dog --- out delete from "cat" where "cat" > $y$dog$y$ === TEST 4: basic delete --- sql delete from Foo where col>$id; --- vars: id --- models: Foo --- in_vars id=hello --- out delete from "Foo" where "col" > $y$hello$y$ === TEST 5: basic update --- sql update $blah set $foo=$blah+1 --- models: cat --- vars: blah foo blah --- in_vars blah=cat foo=dog --- out update "cat" set "dog" = ("cat" + 1) === TEST 6: basic update --- sql update Foo set col=col+1 --- models: Foo --- out update "Foo" set "col" = ("col" + 1) === TEST 7: update with delete --- sql update Foo set foo = $foo where $foo>$bar and $foo like '%hey' ; ; delete from $foo where $foo=5 ; --- in_vars foo=Cat bar=Dog --- out update "Foo" set "foo" = $y$Cat$y$ where ($y$Cat$y$ > $y$Dog$y$ and $y$Cat$y$ like '%hey') delete from "Cat" where "Cat" = 5 === TEST 8: simple GET --- sql GET $bah; --- vars: bah --- in_vars bah=/=/version --- out GET /=/version === TEST 9: GET with expr --- sql GET ( '/=/'||$foo) || $foo --- vars: foo foo --- out GET /=/ === TEST 10: GET with expr (with vars) --- sql GET ( '/=/'||$foo) || $foo --- vars: foo foo --- in_vars foo=version/ --- out GET /=/version/version/ === TEST 11: GET with expr --- sql GET ( '/=/'|| 'ver') || 'sion' --- out GET /=/version === TEST 12: simple POST --- sql POST '/=/model/Post/~/~' || $foo { $foo: "hello" || $foo } --- in_vars foo= --- out POST /=/model/Post/~/~ {"": "hello"} === TEST 13: simple POST (nonempty foo) --- sql POST '/=/model/Post/~/~' || $foo { $foo: "hello" || $foo } --- in_vars foo=.json --- out POST /=/model/Post/~/~.json {".json": "hello.json"} === TEST 14: POST a simple array --- sql POST '/=/model/Post/' || '~/~' [1,$foo,2.5,"hi"||$foo] --- in_vars foo=, world --- out POST /=/model/Post/~/~ [1, ", world", 2.5, "hi, world"] === TEST 15: PUT a literal --- sql PUT '/=/foo' $foo --- vars: foo --- models: --- out PUT /=/foo "" === TEST 16: PUT a hash of lists of hashes --- sql POST '/=/model/~' { "description": $type, "columns": [ { "name": "name", $type: 'text'}, { "name":"created",$type:"timestamp (0) with time zone", default: [$type] } ] } --- vars: type type type type --- in_vars type=bigint --- out POST /=/model/~ {"description": "bigint", "columns": [{"name": "name", "bigint": "text"}, {"name": "created", "bigint": "timestamp (0) with time zone", "default": ["bigint"]}]} === TEST 17: with variables and some noises --- sql update Post set comments = comments + 1 where id = $post_id; POST '/=/model/Comment/~/~' { "sender": $sender, "body": $body, "$post_id": $post_id }; --- vars: post_id sender body post_id --- in_vars post_id=32 sender=agentzh body=Hello, world! --- models: Post --- out update "Post" set "comments" = ("comments" + 1) where "id" = $y$32$y$ POST /=/model/Comment/~/~ {"sender": "agentzh", "body": "Hello, world!", "$post_id": "32"} === TEST 18: try delete --- sql DELETE '/=/model' || $foo; DELETE '/=/view'; DELETE $foo --- models: --- vars: foo foo --- in_vars foo=/=/hi --- out DELETE /=/model/=/hi DELETE /=/view DELETE /=/hi