The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

use strict;
use Test::More tests => 110;

BEGIN{use_ok('CGI::Application');}

# Need CGI.pm for tests
use CGI;

# bring in testing hierarchy
use lib 't/lib';
use TestApp;
use TestApp2;
use TestApp3;
use TestApp4;
use TestApp5;

$ENV{CGI_APP_RETURN_ONLY} = 1;

sub response_like {
	my ($app, $header_re, $body_re, $comment) = @_;

	local $ENV{CGI_APP_RETURN_ONLY} = 1;
	my $output = $app->run;
	my ($header, $body) = split /\r\n\r\n/m, $output;
	like($header, $header_re, "$comment (header match)");
	like($body,	 $body_re,	 "$comment (body match)");
}

# Instantiate CGI::Application
# run() CGI::Application object.	Expect header + output dump_html()
{
	my $app = CGI::Application->new();
	isa_ok($app, 'CGI::Application');

	$app->query(CGI->new(""));
	my $output = $app->run();

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Query Environment:/,
		'base class response',
	);
}

# Instantiate CGI::Application sub-class.
# run() CGI::Application sub-class. 
# Expect HTTP header + 'Hello World: basic_test'.
{
	my $app = TestApp->new(QUERY => CGI->new(""));
	isa_ok($app, 'CGI::Application');

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: basic_test/,
		'TestApp, blank query',
	);
}


# Non-hash references are invalid for PARAMS.
{
  my $app = eval { TestApp->new(PARAMS => [ 1, 2, 3, ]); };

  like($@, qr/not a hash ref/, "PARAMS must be a hashref!");
}

# run() CGI::Application sub-class, in run mode 'redirect_test'.
# Expect HTTP redirect header + 'Hello World: redirect_test'.
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'redirect_test'}));

	response_like(
		$app,
		qr/^Status: 302/,
		qr/Hello World: redirect_test/,
		'TestApp, redirect_test'
	);
}


# run() CGI::Application sub-class, in run mode 'redirect_test'.
# Expect HTTP redirect header + 'Hello World: redirect_test'.
# ...just like the test above, but we pass QUERY in via a hashref.
{
	my $app = TestApp->new({
    QUERY => CGI->new({'test_rm' => 'redirect_test'})
  });

	response_like(
		$app,
		qr/^Status: 302/,
		qr/Hello World: redirect_test/,
		'TestApp, redirect_test'
	);
}

# run() CGI::Application sub-class, in run mode 'dump_text'.
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'dump_txt'}));

	response_like(
		$app,
		qr{^Content-type: text/html}i,
		qr/Query Environment/,
		'TestApp, dump_text'
	);
}


# run() CGI::Application sub-class, in run mode 'cookie_test'. 
# Expect HTTP header w/ cookie:
#	 'c_name' => 'c_value' + 'Hello World: cookie_test'.
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'cookie_test'}));

	response_like(
		$app,
		qr/^Set-Cookie: c_name=c_value/,
		qr/Hello World: cookie_test/,
		"TestApp, cookie test",
	);
}


# run() CGI::Application sub-class, in run mode 'tmpl_test'. 
# Expect HTTP header + 'Hello World: tmpl_test'.
{
	my $app = TestApp->new(TMPL_PATH=>'t/lib/templates/');
	$app->query(CGI->new({'test_rm' => 'tmpl_test'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/---->Hello World: tmpl_test<----/,
		"TestApp, tmpl_test",
	);
}


# run() CGI::Application sub-class, in run mode 'tmpl_badparam_test'.
# Expect HTTP header + 'Hello World: tmpl_badparam_test'.
{
	my $app = TestApp->new(TMPL_PATH=>'t/lib/templates/');
	$app->query(CGI->new({'test_rm' => 'tmpl_badparam_test'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/---->Hello World: tmpl_badparam_test<----/,
		"TestApp, tmpl_badparam_test",
	);
}


# Instantiate and call run_mode 'eval_test'.	Expect 'eval_test OK' in output.
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'eval_test'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: eval_test OK/,
		"TestApp, eval_test",
	);
}

# Test to make sure cgiapp_init() was called in inherited class.
{
	my $app = TestApp2->new();
	my $init_state = $app->param('CGIAPP_INIT');
	ok(defined($init_state), "TestApp2's cgiapp_init ran");
	is($init_state, 'true', "TestApp2's cgiapp_init set the right value");
}


# Test to make sure mode_param() can contain subref
{
	my $app = TestApp3->new();
	$app->query(CGI->new({'go_to_mode' => 'subref_modeparam'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: subref_modeparam OK/,
		"TestApp3, subref_modeparam",
	);
}

# Test to make sure that "false" (but >0 length) run modes are valid -- will
# not default to start_mode()
{
	my $app = TestApp3->new();
	$app->query(CGI->new({'go_to_mode' => '0'}));
	
	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: zero_mode OK/,
		"TestApp3, 0 as run mode isn't start_mode",
	);
}


# A blank mode_param value isn't useful; we fall back to start_mode.
{
	my $app = TestApp3->new();
 	$app->query(CGI->new({'go_to_mode' => ''}));
 	
 	response_like(
 		$app,
 		qr{^Content-Type: text/html},
 		qr/Hello World: default_mode OK/,
 		"TestApp3, q() as run mode is start_mode",
 	);
}

# Test to make sure that undef run modes will default to start_mode()
{
	my $app = TestApp3->new();
	$app->query(CGI->new({'go_to_mode' => 'undef_rm'}));
	
	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: default_mode OK/,
		"TestApp3, undef run mode (goes to start_mode)",
	);
}

# Test run modes returning scalar-refs instead of scalars
{
	my $app = TestApp4->new(QUERY=>CGI->new(""));
	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: subref_test OK/,
		"run modes can return scalar references",
	);
}


# Test "AUTOLOAD" run mode
{
	my $app = TestApp4->new();
	$app->query(CGI->new({'rm' => 'undefined_mode'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: undefined_mode OK/,
		"AUTOLOAD run mode",
	);
}


# what if there is no AUTOLOAD?
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'undefined_mode'}));

  my $output = eval { $app->run };
  like($@, qr/No such run mode/, "no runmode + no autoload = exception");
}


# Can we incrementally add run modes?
# XXX: I don't see how this code tests that question. -- rjbs, 2006-06-30
{
	my $app;
	my $output;

	# Mode: BasicTest
	$app = TestApp5->new();
	$app->query(CGI->new({'rm' => 'basic_test1'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: basic_test1/,
		"force basic_test1",
	);

	# Mode: BasicTest2
	$app = TestApp5->new();
	$app->query(CGI->new({'rm' => 'basic_test2'}));
	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: basic_test2/,
		"force basic_test2",
	);

	# Mode: BasicTest3
	$app = TestApp5->new();
	$app->query(CGI->new({'rm' => 'basic_test3'}));
	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/Hello World: basic_test3/,
		"force basic_test3",
	);
}


# Can we add params in batches?
{
	my $app = TestApp5->new(
		PARAMS => {
			P1 => 'one',
			P2 => 'two'
		}
	);

	# Do params set via new still get set?
	my @plist = sort $app->param();
	is_deeply(\@plist, ['P1', 'P2'], "Pn params set during initialization");

	my @params = (
		'', 'one', 'two', 'new three', 'four', 'new five', 'six', 'seven', 'eight'
	);

	is($app->param("P$_"), $params[$_], "P$_ of 2 correct") for 1..2;

	# Can we still augment params one at a time?
	$app->param('P3', 'three');
	@plist = sort $app->param();
	is_deeply(\@plist, ['P1', 'P2', 'P3'], 'added one param to list');
	is($app->param("P$_"), $params[$_], "P$_ of 2 correct again") for 1..2;
	is($app->param("P3"), 'three', "and new arg, P3, is also correct");

	# Does a list of pairs work?
	my $pt3val = $app->param(
		'P3' => 'new three',
		'P4' => 'four',
		'P5' => 'five'
	);
	@plist = sort $app->param();
	is_deeply(\@plist, ['P1', 'P2', 'P3', 'P4', 'P5'], "all five args set ok");
	is($app->param("P$_"), $params[$_], "P$_ of 4 correct") for 1..4;
	is($app->param("P5"), 'five', "P5 also correct");

	# XXX: Do we really want to test for this?  Maybe we want to change this
	# behavior, on which hopefully nothing but this test depends...
	# -- rjbs, 2006-06-30
	ok(not(defined($pt3val)), "multiple param setting returns undef (for now)");


	# What about a hash-ref?	(Should return undef)
	my $pt4val = $app->param({
		'P5' => 'new five',
		'P6' => 'six',
		'P7' => 'seven',
	});
	@plist = sort $app->param();
	is_deeply(\@plist, ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7'], "7 params ok");
	is($app->param("P$_"), $params[$_], "P$_ of 7 correct") for 1..7;
	ok(not(defined($pt4val)), "multiple param setting returns undef (for now)");

	# What about a simple pass-through?	(Should return param value)
	my $pt5val = $app->param('P8', 'eight');
	@plist = sort $app->param();
	is_deeply(\@plist, [qw(P1 P2 P3 P4 P5 P6 P7 P8)], "P1-8 all ok");
	is($app->param("P$_"), $params[$_], "P$_ of 8 correct") for 1..8;
	is($pt5val, 'eight', "value returned on setting P8 is correct");
}


# test undef param values
{
  my $app = TestApp->new();

  $app->param(foo => 10);

  is(
    $app->delete,
    undef,
    "we get undef when deleting unnamed param",
  );

  is($app->param('foo'), 10, q(and our real param is still ok));
}

# test setting header_props before header_type 
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'props_before_redirect_test'}));
	my $output = $app->run();

	like($output, qr/test: 1/i, "added test header before redirect");
	like($output, qr/Status: 302/, "and still redirected");
}

# testing setting header_props more than once
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'header_props_twice_nomerge'}));
	my $output = $app->run();

	like($output, qr/test: Updated/i, "added test header");
	unlike($output, qr/second-header: 1/, "no second-header header");
	unlike($output, qr/Test2:/, "no Test2 header, either");
}

# testing header_add with arrayref
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'header_add_arrayref_test'}));
	my $output = $app->run();

	like($output, qr/Set-Cookie: cookie1=header_add/, "arrayref test: cookie1");
	like($output, qr/Set-Cookie: cookie2=header_add/, "arrayref test: cookie2");
}

# make sure header_add does not clobber earlier headers
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'header_props_before_header_add'}));
	my $output = $app->run();

	like($output, qr/Set-Cookie: cookie1=header_props/, "header_props: cookie1");
	like($output, qr/Set-Cookie: cookie2=header_add/,   "header_add: cookie2");
}

# make sure header_add works after header_props is called
{
	my $app = TestApp->new();
	$app->query(CGI->new({'test_rm' => 'header_add_after_header_props'}));
	my $output = $app->run();

	like($output, qr/Set-Cookie: cookie2=header_add/, "header add after props");
}

# test use of TMPL_PATH without trailing slash
{
	my $app = TestApp->new(TMPL_PATH=>'t/lib/templates');
	$app->query(CGI->new({'test_rm' => 'tmpl_badparam_test'}));

	response_like(
		$app,
		qr{^Content-Type: text/html},
		qr/---->Hello World: tmpl_badparam_test<----/,
		"TMPL_PATH without trailing slash",
	);
}


# If called "too early" we get undef for current runmode.
{
  my $app = CGI::Application->new;

  eval { $app->run_modes('whatever') };

  like($@, qr/odd number/i, "croak on odd number of args to run_modes");
}


# If called "too early" we get undef for current runmode.
{
  my $app = CGI::Application->new;
  is($app->get_current_runmode, undef, "current runmode is undef before run");
  
  my $dump = $app->dump;
  like($dump, qr/^Current Run mode: ''\n/, "no current run mode in dump");
}


# test delete() method by first setting some params and then deleting them
{
	my $app = TestApp5->new();
	$app->param(
		P1 => 'one',
		P2 => 'two',
		P3 => 'three'
	);

	is_deeply(
		[ sort $app->param ],
		[ qw(P1 P2 P3) ],
		"we start with P1 P2 P3",
	);

	#a valid delete
	my $p2value = $app->delete('P2');
	my @params = sort $app->param();

	is_deeply(\@params, ['P1', 'P3'], "P2 deletes without incident");
	is($p2value, "two", "and deletion returns the deleted value");

	is($app->param('P1'), 'one', 'P1 still has the right value');

	ok(!defined($app->param('P2')), 'P2 is now undef');
	is_deeply(
		[ sort $app->param ],
		['P1', 'P3'],
		"asking for P2 didn't instantiate it",
	);

	is($app->param('P3'), 'three', 'P3 still has the right value');


	#an invalid delete
	my $result = $app->delete('P4');
	
	ok(!defined($result), "we get undef back when deleting nonexistant param");
	is($app->param('P1'), 'one', "and P1's value is unmolested");
	ok(!defined($app->param('P4')), "and the fake param doesn't get a value");
	is($app->param('P3'), 'three', "and P3 is unmolested too");
}

###

my $t27_ta_obj = CGI::Application->new(
	TMPL_PATH => [qw(t/lib/templates /some/other/test/path)]
);
my ($t1, $t2) = (0,0);
my $tmpl_path = $t27_ta_obj->tmpl_path();

ok((ref $tmpl_path eq 'ARRAY'), 'tmpl_path returns array ref');
is($tmpl_path->[0], 't/lib/templates', 'tmpl_path first element is correct');
is($tmpl_path->[1], '/some/other/test/path', 'tmpl_path second element is correct');

my $tmpl = $t27_ta_obj->load_tmpl('test.tmpl');
$tmpl_path = $tmpl->{options}->{path};

ok((ref $tmpl_path eq 'ARRAY'), 'tmpl_path from H::T obj returns array ref');
ok(($tmpl_path->[0] eq 't/lib/templates'), 'tmpl_path from H::T obj first element is correct');
ok(($tmpl_path->[1] eq '/some/other/test/path'), 'tmpl_path from H::T obj second element is correct');

# All done!