webgui/t/Session/Http.t
Scott Walters 7a994b59ce File assets should always give IO::File::WithPath objects to PSGI, instead of the current redirecting or streaming behavior. (#11688)
New API method:  WebGUI::Response::sendFile;  it, as appropriate, calls
setRedirect or setStreamedFile, depending on enableStreamingUploads config var.
setStreamedFile now kicks off the XSendfile process.
File.pm now uses this instead of trying to set both a redirect and a stream.
IO::File::WithPath blows up if a file doesn't exist so this raises an exception
now.
The http now no longer insist that '0' is not a valid filename to stream.
site.psgi, depending on enableStreamingUploads, enables either the Static
or XSendfile middleware.
2011-05-12 20:22:43 -04:00

461 lines
14 KiB
Perl

#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
# this test file is now slightly badly named since the functions in
# WebGUI::Session::HTTML have all been migrated to
# WebGUI::Session::Request and ::Response. still, these tests need
# to continue to pass.
use strict;
use WebGUI::Test;
use WebGUI::Session;
use HTML::TokeParser;
use DateTime;
use DateTime::Format::Strptime;
use Data::Dumper;
use Test::More; # increment this value for each test you create
use Test::Deep;
my $session = WebGUI::Test->session;
my $http = $session->http;
my $response = $session->response;
use Test::MockObject::Extends;
##This tests mocks http's getCookies so that it doesn't have to
##try and implement the mod_perl cookie handling code.
$http = Test::MockObject::Extends->new($http);
my $cookieName = $session->config->getCookieName;
my $varId = $session->getId();
$http->mock( getCookies => sub { return {$cookieName => $varId} } );
isa_ok($http, 'WebGUI::Session::Http', 'session has correct object type');
####################################################
#
# isRedirect
#
####################################################
$response->status('200');
ok(!$http->isRedirect, 'isRedirect: 200 is not');
$response->status('301');
ok($http->isRedirect, '... 301 is');
$response->status('302');
ok($http->isRedirect, '... 302 is too');
$response->status('200');
####################################################
#
# setStreamedFile, getStreamedFile
#
####################################################
$http->setStreamedFile('');
is($http->getStreamedFile, undef, 'set/get StreamedFile: false values return undef, empty string');
$http->setStreamedFile(undef);
is($http->getStreamedFile, undef, 'set/get StreamedFile: false values return undef, empty string');
my $actual_file = $session->config->get('uploadsPath') . '/9e/a3/9ea37e148e517d4ae3d6326f691d848f/previous.gif'; # arbitrary file that exactually exists and hopefully will continue for a while
$http->setStreamedFile( $actual_file );
is($http->getStreamedFile, $actual_file, 'set/get StreamedFile: set specific location and get it');
do {
eval {
$http->setStreamedFile( $actual_file . '_but_actually_not_an_actual_file_because_someone_appended_a_bunch_of_bloody_garbage_to_it' );
};
my $e = WebGUI::Error->caught("WebGUI::Error::InvalidFile");
my $errorMessage = $e->error;
ok($errorMessage =~ m/No such file or directory/, "set/get StreamedFile: setting a non-existant file blows stuff up but that's okay because it's handled gracefully" );
};
$http->setStreamedFile('');
####################################################
#
# setLastModified, getLastModified
#
####################################################
is($http->getLastModified, undef, 'getLastModified: default is undef');
$http->setLastModified(12);
is($http->getLastModified, 12, 'set/get LastModified: epoch date set');
$http->setLastModified(undef);
####################################################
#
# setCacheControl, getCacheControl
#
####################################################
is($http->getCacheControl, 1, 'getCacheControl: default is 1');
$http->setCacheControl("none");
is($http->getCacheControl, "none", 'set/get CacheControl: set to "none"');
$http->setCacheControl(7200);
is($http->getCacheControl, 7200, 'set/get CacheControl: set to 7200');
$http->setCacheControl(0);
is($http->getCacheControl, 1, 'set/get CacheControl: set to 0 returns 1');
$http->setCacheControl(undef);
####################################################
#
# setRedirect
#
####################################################
$session->request->uri('/here/later');
$http->setRedirect('/here/now');
is($response->status, 302, 'setRedirect: sets HTTP status');
is($response->location, '/here/now', 'setRedirect: redirect location');
$session->style->useEmptyStyle(1);
my $styled = $session->style->generateAdditionalHeadTags();
my @metas = fetchMultipleMetas($styled);
my $expectedMetas = [
{
'http-equiv' => 'refresh',
'content' => '0; URL=/here/now'
},
];
cmp_bag(\@metas, $expectedMetas, 'setRedirect:sets meta tags in the style object');
$session->request->uri('/here/now');
$session->url->{_requestedUrl} = '';
my $sessionAsset = $session->asset;
$session->{_asset} = WebGUI::Asset->getDefault($session);
my $defaultAssetUrl = $session->asset->getUrl;
is($http->setRedirect($defaultAssetUrl), undef, 'setRedirect: returns undef if returning to self and no params');
$session->request->setup_body({ param1 => 'value1' });
isnt($http->setRedirect('/here/now'), undef, 'setRedirect: does not return undef if returning to self but there are params');
$session->{_asset} = $sessionAsset;
####################################################
#
# setNoHeader and sendHeader
#
####################################################
##Force settings
$session->setting->set('preventProxyCache', 0);
##Clear request object for next two tests
$session->{_request} = undef;
is($http->getNoHeader, undef, 'getNoHeader: defaults to undef');
$http->setNoHeader(1);
is($http->getNoHeader, 1, 'get/set NoHeader: returns set value');
is($http->sendHeader, undef, 'sendHeader returns undef when setNoHeader is true');
$http->setNoHeader(0);
is($http->sendHeader, undef, 'sendHeader returns undef when no request object is available');
####################################################
#
# sendHeader, redirect
#
####################################################
{
##A new, clean session
my $session1 = WebGUI::Test->newSession('noCleanup');
my $guard = WebGUI::Test->cleanupGuard($session1);
$session1->http->setRedirect('/here/there');
$session1->http->sendHeader;
is($session1->response->status, 302, 'sendHeader as redirect: status set to 301');
cmp_deeply(
headers_out($session1->response->headers),
{
'Location' => '/here/there',
'Content-Type' => 'text/html; charset=UTF-8',
},
'... location set'
);
}
####################################################
#
# sendHeader, Status, LastModified, default mime-type, cache headers.
#
####################################################
{
##A new, clean session
my $session = WebGUI::Test->newSession('nocleanup');
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$response->status(200);
$session->request->protocol('');
$http->setLastModified(1200);
$http->sendHeader();
is($response->status, 200, 'sendHeader: status set');
cmp_deeply(
[ $response->content_type ],
[ 'text/html', 'charset=UTF-8']
, '... default mimetype'
);
cmp_deeply(
headers_out($response->headers),
{
'Last-Modified' => $session->datetime->epochToHttp(1200),
'Cache-Control' => 'must-revalidate, max-age=1',
'Content-Type' => 'text/html; charset=UTF-8',
},
'... normal headers'
);
}
####################################################
#
# sendHeader, mime-type, filename/attachment, recent HTTP protocol
#
####################################################
{
##A new, clean session
my $session = WebGUI::Test->newSession('nocleanup', { SERVER_PROTOCOL => 'HTTP 1.1', });
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$response->header( 'Content-Disposition' => qq{attachment; filename="image.png"});
$response->content_type('image/png');
$http->sendHeader();
is($response->headers->content_type, 'image/png', 'sendHeader: mimetype');
cmp_deeply(
headers_out($response->headers),
{
'Last-Modified' => $session->datetime->epochToHttp(time()),
'Content-Disposition' => q!attachment; filename="image.png"!,
'Content-Type' => 'image/png',
'Cache-Control' => 'must-revalidate, max-age=1'
},
'... normal headers'
);
}
####################################################
#
# sendHeader, old HTTP protocol
#
####################################################
{
##A new, clean session
my $http_request = HTTP::Request::Common::GET('http://'.$session->config->get('sitename')->[0]);
$http_request->protocol('HTTP/1.0');
my $session = WebGUI::Test->newSession('nocleanup', $http_request);
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
my $time = $session->datetime->epochToHttp(time());
$http->sendHeader();
my $headers = headers_out($response->headers);
my $expire_header = $headers->{Expires};
my $delta = deltaHttpTimes($session->datetime->epochToHttp(), $expire_header);
cmp_ok($delta->seconds, '<=', 1, 'sendHeader, old HTTP protocol: adds extra cache header field');
cmp_deeply(
$headers,
{
'Last-Modified' => ignore(),
'Cache-Control' => 'must-revalidate, max-age=1',
'Content-Type' => 'text/html; charset=UTF-8',
'Expires' => ignore(),
},
'... checking headers'
);
}
####################################################
#
# sendHeader, old HTTP protocol, cacheControl set to 500
#
####################################################
{
##A new, clean session
my $http_request = HTTP::Request::Common::GET('http://'.$session->config->get('sitename')->[0]);
$http_request->protocol('HTTP/1.0');
my $session = WebGUI::Test->newSession('nocleanup', $http_request);
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$http->setCacheControl(500);
$http->sendHeader();
my $headers = headers_out($response->headers);
my $expire_header = $headers->{Expires};
my $delta = deltaHttpTimes($session->datetime->epochToHttp(time+500), $expire_header);
cmp_ok($delta->seconds, '<=', 2, 'sendHeader, old HTTP protocol, cacheControl=500: adds extra cache header field');
cmp_deeply(
$headers,
{
'Last-Modified' => ignore(),
'Cache-Control' => 'must-revalidate, max-age=500',
'Content-Type' => 'text/html; charset=UTF-8',
'Expires' => ignore(),
},
'... checking headers'
);
}
####################################################
#
# sendHeader, preventProxyCache changes cache headers
#
####################################################
##Clear request object to run a new set of requests
{
##A new, clean session
my $session = WebGUI::Test->newSession('nocleanup');
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$session->setting->set('preventProxyCache', 1);
$http->sendHeader();
cmp_deeply(
headers_out($response->headers),
{
'Content-Type' => 'text/html; charset=UTF-8',
'Cache-Control' => 'private, max-age=1, no-cache',
'Pragma' => 'no-cache',
},
'sendHeader: Cache-Control setting when preventProxyCache set'
);
$session->setting->set('preventProxyCache', 0);
}
####################################################
#
# sendHeader, cacheControl=none changes cache headers
#
####################################################
{
##A new, clean session
my $session = WebGUI::Test->newSession('nocleanup');
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$http->setCacheControl('none');
$http->sendHeader();
cmp_deeply(
headers_out($response->headers),
{
'Cache-Control' => 'private, max-age=1, no-cache',
'Content-Type' => 'text/html; charset=UTF-8',
'Pragma' => 'no-cache',
},
'sendHeader: Cache-Control setting when cacheControl="none"'
);
}
####################################################
#
# sendHeader, non-visitor user changes cache headers
#
####################################################
{
##A new, clean session
my $session = WebGUI::Test->newSession('nocleanup');
my $guard = WebGUI::Test->cleanupGuard($session);
my $http = $session->http;
my $response = $session->response;
$session->user({userId => 3});
$http->sendHeader();
cmp_deeply(
headers_out($response->headers),
{
'Cache-Control' => 'private, max-age=1, no-cache',
'Content-Type' => 'text/html; charset=UTF-8',
'Pragma' => 'no-cache',
},
'sendHeader: Cache-Control setting when user is not Visitor'
);
}
done_testing;
####################################################
#
# Utility functions
#
####################################################
=head2 fetchMultipleMetas ($text)
Parse a piece of text as HTML, and extract out all the meta tags from it. Returns the meta
tags as a list.
=cut
sub fetchMultipleMetas {
my ($text) = @_;
my $p = HTML::TokeParser->new(\$text);
my @metas = ();
while (my $token = $p->get_tag('meta')) {
my $params = $token->[1];
delete $params->{'/'}; ##delete unary slash from XHTML output
push @metas, $params;
}
return @metas;
}
=head2 deltaHttpTimes ($http1, $http2)
Takes two dates in HTTP format, and returns $http1 - $http2
=cut
sub deltaHttpTimes {
my ($http1, $http2) = @_;
my $httpParser = DateTime::Format::Strptime->new(pattern =>'%a, %d %b %Y %H:%M:%S', time_zone => 'GMT');
my $dt1 = $httpParser->parse_datetime($http1);
my $dt2 = $httpParser->parse_datetime($http2);
my $delta_time = $dt1-$dt2;
}
=head2 headers_out ($header_object)
Returns an array reference of HTTP headers, as hashrefs, from a HTTP::Headers object, to make comparison with
Test::Deep easier.
=cut
sub headers_out {
my ($head_obj) = @_;
my $headers = {};
foreach my $field_name ($head_obj->header_field_names) {
$headers->{$field_name} = $head_obj->header($field_name);
}
return $headers;
}