Bivio::Test::Request
# Copyright (c) 2002-2017 bivio Software, Inc. All Rights Reserved.
# $Id$
package Bivio::Test::Request;
use strict;
use Bivio::Base 'AgentJob.Request';
use Socket ();
my($_B) = b_use('Test.Bean');
my($_RI) = b_use('Agent.RequestId');
b_use('Bivio.Test')->register_handler(__PACKAGE__);
my($_CL) = b_use('IO.ClassLoader');
sub agent_execution_is_secure {
return 1;
}
sub capture_mail {
my($self) = @_;
$self->unsafe_get_captured_mail;
return $self;
}
sub client_redirect {
my($self) = _redirect_check(shift);
$self->server_redirect(@_);
return $self->put_durable(initial_uri => $self->get('uri'));
}
sub commit {
return b_use('Agent.Task')->commit(shift(@_));
}
sub delete_class_from_self {
my($self, $class) = @_;
$self->delete(
$class,
$self->use($class),
);
return;
}
sub execute_task {
my($self) = shift->initialize_fully(@_);
#TODO: hacked - remove the list model or PropertyModel::_parse_query() fails
$self->req->delete('list_model');
$self->capture_mail;
$self->get('task')->execute($self);
my($o) = $self->get('reply')->delete_output;
return [$o ? $$o : undef, @{$self->unsafe_get_captured_mail}];
}
sub format_http_toggling_secure {
my($self) = @_;
return '';
}
sub get_current_or_new {
my($proto) = @_;
my($self) = $proto->get_current;
return $self
if $self;
$self = $proto->new({})->put_durable(
auth_id => undef,
auth_user_id => undef,
task_id => b_use('Agent.TaskId')->SHELL_UTIL,
timezone => b_use('Type.DateTime')->timezone,
is_secure => 0,
disable_assert_cookie => 1,
reply => b_use('Test.Reply')->new,
);
$self->set_realm(undef);
$self->set_user(undef);
return $self;
}
sub get_form {
return shift->unsafe_get('form');
}
sub get_instance {
my($self) = shift->get_current_or_new(@_);
$self->require_no_cookie
if $self->can('require_no_cookie');
return $self;
}
sub handle_prepare_case {
my($proto) = @_;
return
unless my $self = $proto->get_current;
$_RI->clear_current($self);
return;
}
sub initialize_fully {
my($self) = shift(@_);
$self = $self->get_instance
unless ref($self);
_redirect_check($self, 1);
my($task_id, $req_attrs, $facade_name) = @_;
($req_attrs ||= {})->{task_id} = b_use('Agent.TaskId')->from_any(
$task_id || $self->unsafe_get('task_id') || 'SHELL_UTIL');
b_use('Agent.Dispatcher')->initialize;
if ($facade_name) {
$self->setup_facade($facade_name);
}
else {
$self->setup_all_facades;
}
b_die(
'facade not fully initialized; this method must be called before'
. ' any setup_facade or Bivio::ShellUtil->initialize_ui'
) unless b_use('UI.Facade')->is_fully_initialized;
$self->put_durable(%$req_attrs);
$self->set_task_and_uri({
map(exists($req_attrs->{$_}) ? ($_ => $req_attrs->{$_}) : (),
@{$self->FORMAT_URI_PARAMETERS}),
});
return $self;
}
sub internal_redirect_user_realm {
shift->throw_die(FORBIDDEN => {
entity => shift,
message => 'no realm to guess',
});
# DOES NOT RETURN
}
sub is_test {
my($self) = @_;
return $self->get_or_default('is_test', shift->SUPER::is_test(@_));
}
sub new_unit {
my($proto, $class_name, $method, @args) = @_;
b_die('request already exists: ', $proto->get_current)
if $proto->get_current;
$method ||= 'get_instance';
return $proto->$method(@args)->put(class_name => $class_name);
}
sub put_form {
my($self, $form, $fields) = @_;
return $self->put(form => {
$form->VERSION_FIELD => $form->get_info('version'),
map({
my($f) = $_;
# There are sometimes junk fields in fields of form, e.g.
# ListFormModel fields.
defined($form->get_field_name_for_html($f))
? ($form->get_field_name_for_html($f) =>
$form->get_field_type($f)->to_literal($fields->{$f}))
: ();
} keys(%$fields)),
});
}
sub put_on_query {
my($self) = shift;
my($query) = $self->get_if_defined_else_put(query => {});
$self->map_by_two(sub {
my($k, $v) = @_;
$query->{_maybe_to_char($k)} = $v;
}, \@_);
return $self;
}
sub require_no_cookie {
b_use('IO.Config')->introduce_values({
'Bivio::IO::ClassLoader' => {
delegates => {
'Bivio::Agent::HTTP::Cookie' => 'Bivio::Delegate::NoCookie',
},
},
}) unless $_CL->was_required('Bivio::Agent::HTTP::Cookie');
return shift;
}
sub run_unit {
return shift->use('TestUnit.Unit')->run_unit(@_);
}
sub server_redirect {
my($self) = _redirect_check(shift);
my(undef, $named) = $self->internal_client_redirect_args(@_);
b_die($named, ': uris not supported yet')
if defined($named->{uri});
$self->internal_server_redirect($named);
return;
}
sub set_realm_and_user {
my($self) = shift;
$self = $self->get_instance
unless ref($self);
b_use('Bivio.ShellUtil')->set_realm_and_user(@_);
return $self;
}
sub setup_all_facades {
my($self) = shift->setup_http;
b_use('Agent.Dispatcher')->initialize(0);
return $self->setup_facade;
}
sub setup_facade {
my($proto, $facade) = @_;
my($self) = $proto->setup_http;
b_use('Bivio.ShellUtil')->initialize_ui;
b_use('UI.Facade')->setup_request($facade, $self)
if $facade;
return $self;
}
sub setup_http {
my($self) = @_;
$self = $self->get_instance
unless ref($self);
return $self
if $self->unsafe_get('r');
# What's required by bOP infrastructure.
b_use('Type.UserAgent')->BROWSER_HTML4->execute($self, 1);
my($ip) = '127.0.0.1';
my($addr) = Socket::pack_sockaddr_in(80, Socket::inet_aton($ip));
my($method) = 'GET';
my($content_type);
my($header) = {};
# header_in and header_out have different names
my($header_op) = sub {
my($args) = @_;
return !@$args ? %$header
: @$args > 1 ? ($header->{$args->[0]} = $args->[1])
: $header->{$args->[0]};
};
my($r) = $_B->new({
'connection()' => [$_B->new({
'useragent_ip()' => [$ip],
# Apache 2.2 interface
'remote_ip()' => [$ip],
'local_addr()' => [$addr],
'server()' => [$_B->new({})],
'user()' => [],
})],
'method()' => sub {
my($args) = @_;
return @$args ? ($method = $args->[0]) : $method;
},
'uri()' => ['/'],
'header_in()' => $header_op,
'header_out()' => $header_op,
'headers_out()' => [b_use('Test.Bean')->new({
'add()' => $header_op,
})],
'hostname()' => ['localhost.localdomain'],
'get_server_port()' => [80],
'content_type()' => sub {
my($args) = @_;
return @$args ? ($content_type = $args->[0]) : $content_type;
},
});
$self->put_durable(r => $r);
# Cookie overwrites, so we have to reset below
my($user) = $self->get('auth_user');
$self->put_durable(
uri => '/',
initial_uri => '/',
path_info => $self->unsafe_get('path_info'),
query => $self->unsafe_get('query'),
cookie => b_use('AgentHTTP.Cookie')->new($self, $r),
client_addr => $ip,
user_state => b_use('Type.UserState')->JUST_VISITOR,
);
# Sets user after cookie clears it
if ($user) {
if ($user->is_default) {
$self->set_user($user);
}
else {
b_use('Model.UserLoginForm')->execute($self, {
realm_owner => $user,
});
}
$self->put_durable(user_state => $self->get('user_state')->LOGGED_IN);
}
b_use('Action.JobBase')->set_sentinel($self);
return $self;
}
sub set_user_state_and_cookie {
my($self, $user_state, $user) = @_;
$user_state = b_use('Type.UserState')->from_any($user_state);
$self->put_unless_exists(cookie => b_use('Collection.Attributes')->new);
my($ulf) = b_use('Model.UserLoginForm')->new($self);
$ulf->process({login => $user});
$ulf->process({login => undef})
if $user_state->eq_logged_out;
return $self;
}
sub unsafe_get_captured_mail {
my($self) = @_;
my($res) = [];
$self->put(txn_resources => [
map({
!$_->isa('Bivio::Mail::Outgoing') ? $_
: (sub {push(@$res, shift->as_string); return})->($_);
} @{$self->get('txn_resources')}),
]);
return $res;
}
sub _maybe_to_char {
my($key) = @_;
my($die);
return b_use('Bivio.Die')->catch(sub {
return b_use('SQL.ListQuery')->to_char($key);
}, \$die) || $key;
}
sub _redirect_check {
my($self, $reset) = @_;
my($n) = ref($self) . '._redirect_check';
if ($reset) {
$self->delete($n);
return;
}
my($r) = $self->get_if_exists_else_put($n => 0);
if ((caller(1))[0]->isa('Bivio::Agent::Request')) {
# high number b/c client_redirect calls server_redirect
$self->throw_die(DIE => {
message => 'too many redirects',
}) if ++$r > 10;
}
else {
$r = 1;
}
return $self->put($n => $r);
}
1;