Cmd-Dwarf/share/app/lib/App/Test.pm
package App::Test;
use Dwarf::Pragma;
use parent 'Exporter';
use Data::Dumper;
use Encode qw/decode_utf8/;
use JSON;
use HTTP::Cookies;
use HTTP::Request::Common qw/GET HEAD PUT POST DELETE/;
use LWP::UserAgent;
use Plack::Test;
use Test::More;
use WWW::Mechanize;
use App;
our @EXPORT = qw/is_success is_failure/;
sub import {
my ($pkg) = @_;
Dwarf::Pragma->import();
Test::More->import();
Test::More->export_to_level(1);
Plack::Test->import();
Plack::Test->export_to_level(1);
}
sub is_success {
my ($res, $path) = @_;
my $desc = $res->status_line;
$desc .= ', redirected to ' . ($res->header("Location") || "") if ($res->is_redirect);
if (!$res->is_redirect) {
warn Dumper $res unless $res->is_success;
ok $res->is_success, "$path: $desc";
} else {
ok $res->is_redirect, "$path: $desc";
}
}
sub decode_response {
my ($res) = @_;
if (($res->code == 200 || $res->code == 400 || $res->code == 500) and $res->header('Content-Type') =~ /json/) {
return $res unless $res->content;
my $content = eval { decode_json($res->content) };
if ($@) {
warn $content;
}
$res->content($content);
return $res;
} elsif ($res->code == 302) {
return $res;
}
return $res;
}
sub is_failure {
my ($res, $path) = @_;
my $desc = $res->status_line;
ok !$res->is_success, "$path: $desc";
}
use Dwarf::Accessor qw/context context_stack cookie_jar ua mech will_decode_content/;
sub _build_context { App->new }
sub _build_context_stack { [] }
sub _build_cookie_jar { HTTP::Cookies->new }
sub _build_ua {
my ($self) = @_;
my $ua = LWP::UserAgent->new;
$ua->cookie_jar($self->cookie_jar);
return $ua;
}
sub _build_mech {
my ($self) = @_;
my $mech = WWW::Mechanize->new(autocheck => 0);
$mech->cookie_jar($self->cookie_jar);
return $mech;
}
sub c { $_[0]->context }
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = bless { @_ }, $class;
$self->{will_decode_content} //= 0;
return $self;
}
sub req_ok {
my ($self, $method, $url, @params) = @_;
my ($req, $res) = $self->req($method, $url, @params);
is_success($res, $req->uri);
return wantarray ? ($req, $res) : $res;
}
sub req_not_ok {
my ($self, $method, $url, @params) = @_;
my ($req, $res) = $self->req($method, $url, @params);
is_failure($res, $req->uri);
return wantarray ? ($req, $res) : $res;
}
sub req {
my ($self, $method, $url, @args) = @_;
my $req = $self->_req($method => $url, @args);
my $res;
test_psgi app => $self->app, client => sub {
my ($cb) = @_;
$self->cookie_jar->add_cookie_header($req);
$res = $cb->($req);
$self->cookie_jar->extract_cookies($res);
};
$res = decode_response($res) if $self->will_decode_content;
return wantarray ? ($req, $res) : $res;
}
sub _req {
my ($self, $method, $url, @args) = @_;
if ($self->c->conf('ssl')) {
$url =~ s/^http/https/;
}
my $uri = URI->new($url);
$uri->query_form($args[0]) if $method =~ /^(get|delete)$/i;
my @a = ($uri->as_string);
push @a, @args if $method !~ /^(get|delete)$/i;
$method = uc $method;
my $req;
# HTTP::Request::Common が PATCH をサポートしてないのでワークアラウンド
if ($method eq 'PATCH') {
$method = 'POST';
$method = \&$method;
$req = $method->(@a);
$req->method('PATCH');
} else {
$method = \&$method;
$req = $method->(@a);
}
return $req;
}
sub ua_ok {
my ($self, $method, $url, @args) = @_;
my $req = $self->_req($method, $url, @args);
my $res = $self->ua->request($req);
$res = decode_response($res) if $self->will_decode_content;
is_success($res, $req->uri);
return wantarray ? ($req, $res) : $res;
}
sub ua_not_ok {
my ($self, $method, $url, @args) = @_;
my $req = $self->_req($method, $url, @args);
my $res = $self->ua->request($req);
$res = decode_response($res) if $self->will_decode_content;
is_failure($res, $req->uri);
return wantarray ? ($req, $res) : $res;
}
sub mech_fetch {
my ($self, $url, $args) = @_;
my $uri = URI->new($url);
$uri->query_form($args) if ref $args eq 'HASH';
my $mech = $self->mech;
$mech->get($uri);
$mech->update_html(decode_utf8($mech->content));
}
sub mech_ok {
my ($self, $url, $args) = @_;
$self->mech_fetch($url, $args);
ok($self->mech->success);
}
sub mech_not_ok {
my ($self, $url, $args) = @_;
$self->mech_fetch($url, $args);
ok(!$self->mech->success);
}
sub mech_submit {
my ($self, $url, $args, $opt) = @_;
my $form_number = $opt->{form_number} // 1;
$self->mech_fetch($url);
my $mech = $self->mech;
$mech->form_number($form_number);
$mech->set_fields(%$args);
$mech->click;
$mech->update_html(decode_utf8($mech->content));
}
sub mech_submit_ok {
my ($self, $url, $args, $opt) = @_;
$self->mech_submit($url, $args, $opt);
my $mech = $self->mech;
ok($mech->success);
}
sub mech_submit_not_ok {
my ($self, $url, $args, $opt) = @_;
$self->mech_submit($url, $args, $opt);
my $mech = $self->mech;
ok(!$mech->success);
}
sub app {
my $self = shift;
return sub {
my $env = shift;
$env->{HTTPS} = 'on';
$env->{HTTP_HOST} = 'localhost';
#$env->{HTTP_AUTHORIZATION} = "Bearer " . $self->c->conf('/oauth/bearer_token');
push @{ $self->{context_stack} }, $self->context if $self->context; # 古いコンテキストを保存して GC されないようにする
$self->{context} = App->new(env => $env);
$self->c->runtime(0) if $self->c->can('runtime'); # runtime プラグインの結果を出力しない
$self->c->to_psgi;
};
}
1;