#!/usr/bin/env perl
use
warnings
qw(FATAL all NONFATAL misc)
;
use
FindBin; BEGIN {
do
"$FindBin::Bin/t_lib.pl"
}
sub
rootname {
my
$fn
=
shift
;
$fn
=~ s/\.\w+$//;
join
""
,
$fn
,
@_
}
use
constant
DEBUG_FACTORY
=>
$ENV
{DEBUG_YATT_FACTORY};
use
constant
DEBUG_REFCNT
=>
$ENV
{DEBUG_YATT_REFCNT};
use
if
DEBUG_REFCNT,
B
=>
qw/svref_2object/
;
BEGIN {
foreach
my
$req
(
qw(Plack Plack::Test Plack::Response HTTP::Request::Common)
) {
unless
(
eval
qq{require $req}
) {
plan
skip_all
=>
"$req is not installed."
;
exit
;
}
}
}
my
$rootname
= untaint_any(
$FindBin::Bin
.
"/"
.rootname(
$FindBin::RealScript
));
sub
is_or_like($$;$) {
my
(
$got
,
$expect
,
$title
) =
@_
;
if
(
ref
$expect
) {
like
$got
,
$expect
,
$title
;
}
else
{
is
$got
,
$expect
,
$title
;
}
}
{
my
$site
= YATT::Lite::WebMVC0::SiteApp
->new(
app_root
=>
$FindBin::Bin
,
doc_root
=>
"$rootname.d"
,
app_ns
=>
'MyYATT'
,
app_base
=> [
'@psgi.ytmpl'
]
,
namespace
=> [
'yatt'
,
'perl'
,
'js'
]
,
use_subpath
=> 1
, (
psgi_fallback
=> YATT::Lite::WebMVC0::SiteApp
->psgi_file_app(
"$rootname.d.fallback"
))
);
printf
STDERR
"# app1.after_new: refcnt=%d\n"
, svref_2object(
$site
)->REFCNT
if
DEBUG_REFCNT && DEBUG_FACTORY;
is
$site
->cget(
'no_unicode'
),
undef
,
"No no_unicode, by default"
;
foreach
my
$cf
(
qw/header_charset output_encoding tmpl_encoding/
) {
is
$site
->cget(
$cf
),
'utf-8'
,
"$cf is utf8 by default"
;
}
my
$app
=
$site
->to_app;
{
my
$client
= Plack::Test->create(
$app
);
sub
test_action (&@) {
my
(
$subref
,
$request
,
%params
) =
@_
;
my
$path
=
$request
->uri->path;
$site
->get_lochandler(dirname(
$path
))
->set_action_handler(basename(
$path
) =>
$subref
);
$client
->request(
$request
,
%params
);
}
test_action {
my
(
$this
,
$con
) =
@_
;
isa_ok
$con
,
"YATT::Lite::WebMVC0::Connection"
;
} GET
"/virt"
;
test_action {
my
(
$this
,
$con
) =
@_
;
is
$con
->param(
'foo'
),
'bar'
,
"param('foo')"
;
} GET
"/virt?foo=bar"
;
undef
*test_action
;
}
{
my
$t
=
sub
{
my
(
$mount_path
,
$req_path
,
$want_pathinfo
,
$more_desc
) =
@_
;
$site
->mount_psgi(
$mount_path
,
sub
{
my
(
$env
) =
@_
;
is
$env
->{PATH_INFO},
$want_pathinfo
,
"mount psgi $mount_path, GET $req_path => PT($want_pathinfo)"
. (
$more_desc
//
''
)
;
[200, [],
"OK"
]
}
);
Plack::Test->create(
$app
)->request(GET
$req_path
)
};
$t
->(
"/mnt"
=>
"/mnt"
=>
""
,
": PT maybe '' for root"
);
$t
->(
"/mnt"
=>
"/mnt/"
=>
"/"
,
": Nonempty PT must start with /"
);
$t
->(
"/mnt/"
=>
"/mnt/"
=>
"/"
,
": Last / in mount path is not trimmed from PT"
);
$t
->(
"/mnt2"
=>
"/mnt2"
=>
""
,
": mnt2, not mnt. Longest must win."
);
}
{
$site
->mount_psgi(
"/cgi-bin/"
,
Plack::App::CGIBin->new(
root
=>
"$rootname.cgi-bin"
)
);
test_psgi
$site
->to_app,
sub
{
my
(
$cb
) =
@_
;
my
$res
=
$cb
->(GET
"/cgi-bin/test1.cgi?foo=bar"
);
is
$res
->content,
"\nQUERY_STRING: foo=bar\n"
;
};
$site
->mount_psgi(
"/cgi-bin/"
,
Plack::App::CGIBin->new(
root
=>
"$rootname.d/cgi-bin"
)
);
test_psgi
$site
->to_app,
sub
{
my
(
$cb
) =
@_
;
my
$res
=
$cb
->(GET
"/cgi-bin/test1.cgi?x=y"
);
is
$res
->content,
"\nUnder doc_root, QUERY_STRING: x=y\n"
;
};
}
{
;;
$site
->mount_static(
"/static"
=>
"$rootname.static"
);
test_psgi
$site
->to_app,
sub
{
my
(
$cb
) =
@_
;
my
$res
=
$cb
->(GET
"/static/test.yatt"
);
is
$res
->content,
"<?perl die?>\n"
;
};
}
my
$hello
=
sub
{
my
(
$id
,
$body
,
$rest
) =
@_
;
$rest
//=
""
;
<<END;
<div id="$id">
Hello $body$rest!
</div>
END
};
my
$out_index
=
$hello
->(
content
=>
'World;-)'
);
my
$out_beta
=
$hello
->(
beta
=>
"world line"
);
foreach
my
$test
([
"/"
, 200,
$out_index
, [
"Content-Type"
,
qq{text/html; charset="utf-8"}
]]
, [
"/index"
, 200,
$out_index
]
, [
"/index.yatt"
, 200,
$out_index
]
, [
"/index.yatt/foo/bar"
, 200,
$hello
->(
content
=>
"Worldfoo/bar"
)]
, [
"/index/foo/bar"
, 200,
$hello
->(
content
=>
"Worldfoo/bar"
)]
, [
"/index/baz/1234"
, 200,
$hello
->(
other
=>
"ok?(1234)"
)]
, [
"/defaction.yatt?!!="
, 200,
"OK"
]
, [
"/defaction.yatt"
, 200,
"OK"
]
, [
"/mid_defaction.yatt?!!="
, 200,
"OK2"
]
, [
"/mid_defaction.yatt"
, 200,
"OK2"
]
, [
"/baz/5678"
, 200,
$hello
->(
other
=>
"ok?(5678)"
)]
, [
"/no_subpath/foobar"
, 404,
qr{No such subpath}
]
, [
"/test.lib/Foo.pm"
, 403,
qr{Forbidden}
]
, [
"/.htaccess"
, 403,
qr{Forbidden}
]
, [
"/hidden.ytmpl"
, 403,
qr{Forbidden}
]
, [
"/beta/world_line"
, 200,
$out_beta
]
, [
"/beta/world_line.yatt"
, 200,
$out_beta
]
, [
"/beta/world_line.yatt/baz"
, 200,
$hello
->(
beta
=>
"bazworld line"
)]
, [
"/beta/world_line/baz"
, 200,
$hello
->(
beta
=>
"bazworld line"
)]
, [
"/beta/world_line.yatt/edit/1234"
, 200,
$hello
->(
edit
=>
"1234's world"
)]
, [
"/beta/world_line/edit/1234"
, 200,
$hello
->(
edit
=>
"1234's world"
)]
, [[
psgi_static
=>
"/test.css"
], 200,
"* {font-size: x-large; }\n"
]
, [[
psgi_fallback
=>
"/beta/fbck.html"
], 200
,
"<h2>Fallback contents, outside of document root</h2>\n"
]
) {
unless
(
defined
$test
) {
$DB::single
= 1;
1
if
$DB::single
;
next
;
}
my
(
$path_or_spec
,
$code
,
$body
,
$header
) =
@$test
;
my
(
$theme
,
$path
) =
do
{
if
(
ref
$path_or_spec
) {
(
"($path_or_spec->[0]) $path_or_spec->[1]"
,
$path_or_spec
->[1]);
}
else
{
(
$path_or_spec
,
$path_or_spec
);
}
};
my
$tuple
=
do
{
my
Env
$env
= Env->psgi_simple_env;
$env
->{PATH_INFO} =
$path
;
$env
->{SCRIPT_NAME} =
''
;
$app
->(
$env
);
};
is
$tuple
->[0],
$code
,
"[code] $theme"
;
is_or_like
join
(
""
,
do
{
if
(
ref
$tuple
->[2] eq
'ARRAY'
) {
@{
$tuple
->[2]}
}
elsif
(
my
$sub
=
$tuple
->[2]->can(
"getlines"
)) {
$sub
->(
$tuple
->[2]);
}
else
{
die
"Unknown tuple type: "
.
ref
(
$tuple
->[2]);
}
}),
$body
,
"[body] $theme"
;
if
(
$header
and
my
@h
=
@$header
) {
my
%header
= @{
$tuple
->[1]};
while
(
my
(
$key
,
$value
) =
splice
@h
, 0, 2) {
is_or_like
$header
{
$key
},
$value
,
"[header][$key] $path"
;
}
}
}
{
my
$path
;
my
$post_env
=
sub
{
my
(
$path
,
$data
) =
@_
;
my
Env
$env
= Env->psgi_simple_env;
$env
->{REQUEST_METHOD} =
"POST"
;
if
((
$env
->{PATH_INFO} =
$path
) =~ s/\?(.*)//) {
$env
->{QUERY_STRING} = $1;
}
$env
->{SCRIPT_NAME} =
''
;
$data
//=
""
;
$env
->{CONTENT_LENGTH} =
length
$data
;
$env
->{CONTENT_TYPE} =
'application/x-www-form-urlencoded'
;
open
my
$fh
,
'<'
, \
$data
or
die
"Can't open memstream: $!"
;
$env
->{
'psgi.input'
} =
$fh
;
$env
;
};
{
my
$theme
=
"POST "
;
my
$res
=
$app
->(
$post_env
->(
my
$p
=
"/?~~=qux"
,
"any=data1"
));
is
$res
->[0], 200,
"$theme $p status"
;
like
join
(
""
, @{
$res
->[2]}),
qr/Qux!/
,
"$theme $p body"
;
}
{
my
$theme
=
"POST redirect "
;
my
$p
=
"/?!!=redir"
;
my
$res
= Plack::Response->new
(@{
$app
->(
$post_env
->(
$p
,
"z=w"
))});
is
$res
->status, 302,
"$theme $p status"
;
is
$res
->headers->header(
'Location'
),
"/qux"
,
"$theme $p location"
;
}
{
my
$theme
=
"render from action"
;
my
$res
=
$app
->(
$post_env
->(
my
$p
=
"/?!!=hello"
,
"any=data"
));
is
$res
->[0], 200,
"$theme $p status"
;
is_or_like
join
(
""
, @{
$res
->[2]})
,
$hello
->(
content
=>
"World from action"
)
,
"$theme $p body"
;
}
}
{
;
my
$theme
=
"[route matching order]"
;
my
$get_env
=
sub
{
my
(
$path
) =
@_
;
my
Env
$env
= Env->psgi_simple_env;
$env
->{PATH_INFO} =
$path
;
$env
->{SCRIPT_NAME} =
''
;
$env
;
};
{
my
$res
=
$app
->(
$get_env
->(
"/index2/doc/"
.(
my
$p
=
"hello.md"
)));
is
$res
->[0], 200,
"$theme $p status"
;
is_or_like
join
(
""
, @{
$res
->[2]})
,
"<h2>Render doc: hello.md</h2>\n"
,
"$theme $p body"
;
}
{
my
$theme
=
"First action should match"
;
my
$res
=
$app
->(
$get_env
->(
"/index2/doc/"
.(
my
$p
=
"hello.gif"
)));
is
$res
->[0], 200,
"$theme $p status"
;
is_or_like
join
(
""
, @{
$res
->[2]})
,
"Action: $p"
,
"$theme $p body"
;
}
}
printf
STDERR
"# app1.end_of_scope: refcnt=%d\n"
, svref_2object(
$site
)->REFCNT
if
DEBUG_REFCNT && DEBUG_FACTORY;
}
{
{
paths
cf_name/
;
sub
startup {
(
my
MY
$self
,
my
$router
,
my
@apps
) =
@_
;
my
$docs
=
$self
->{base_path} =
$router
->cget(
'doc_root'
);
$docs
=~ s,/+$,,;
$docs
= File::Spec->rel2abs(
$docs
);
foreach
my
$app
(
@apps
) {
my
$dir
=
$app
->cget(
'dir'
);
$dir
=~ s/^\Q
$docs
\E//;
$dir
=~ s{\\}{/}g;
push
@{
$self
->{paths}},
$dir
;
}
}
sub
paths {
(
my
MY
$self
) =
@_
;
sort
@{
$self
->{paths}}
}
}
my
$backend
= MyBackend1->new(
name
=>
'backend test'
);
my
$app
= YATT::Lite::WebMVC0::SiteApp
->new(
app_root
=>
$FindBin::Bin
,
doc_root
=>
"$rootname.d"
,
app_ns
=>
'MyYATT2'
,
backend
=>
$backend
)
->to_app;
printf
STDERR
"# app2.after_new: refcnt=%d\n"
, svref_2object(
$app
)->REFCNT
if
DEBUG_REFCNT && DEBUG_FACTORY;
is_deeply [
$backend
->paths]
, [
''
,
qw|/beta /cgi-bin /test.lib|
]
,
"backend startup is called"
;
printf
STDERR
"# app2.end_of_scope: refcnt=%d\n"
, svref_2object(
$app
)->REFCNT
if
DEBUG_REFCNT && DEBUG_FACTORY;
}
{
my
$t
=
sub
{
is_deeply [YATT::Lite::Factory->n_created, YATT::Lite::Factory->n_destroyed]
, [2, 2],
"Site apps are destroyed correctly."
;
};
my
$todo
=
do
{
if
(
grep
(
defined
&& /^-MDevel::Cover/,
$ENV
{HARNESS_PERL_SWITCHES})) {
"This test doesn't work well with Devel::Cover"
;
}
elsif
($] < 5.018) {
"Perl before 5.018 has problem about this test."
;
}
elsif
($] >= 5.040) {
"Perl before 5.040 has problem about this test."
;
}
else
{
}
};
if
(
$todo
) {
TODO: {
local
$TODO
=
$todo
;
$t
->();
}
}
else
{
$t
->();
}
}
done_testing();