The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# -*- mode: cperl; cperl-indent-level: 2; cperl-continued-statement-offset: 2; indent-tabs-mode: nil -*-
use strict;
use warnings FATAL => 'all';

use Apache::Test ();            # just load it to get the version
use version;
use Apache::Test (version->parse(Apache::Test->VERSION)>=version->parse('1.35')
                  ? '-withtestmore' : ':withtestmore');
use Apache::TestUtil;
use Apache::TestRequest qw{GET_BODY GET OPTIONS};
use DBI;
use File::Basename 'dirname';

plan tests=>28;
#plan 'no_plan';

my $serverroot=Apache::Test::vars->{serverroot};
my ($db,$user,$pw)=@ENV{qw/DB USER PW/};
$user='' unless defined $user;
$pw='' unless defined $pw;
my $dbinit='';
unless( defined $db and length $db ) {
  ($db,$user,$pw)=("dbi:SQLite:dbname=$serverroot/test.sqlite", '', '');
  $dbinit="PRAGMA synchronous = OFF";
}
t_debug "Using DB=$db USER=$user";
my $dbh;

my $data;

sub update_db {
  $dbh->do('DELETE FROM trans');

  my $stmt=$dbh->prepare( <<'SQL' );
INSERT INTO trans (id, xkey, xuri, xblock, xorder, xaction) VALUES (?,?,?,?,?,?)
SQL

  my $header=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
1	default	:PRE:	0	1	Do: $r->notes->{t}=$r->notes->{t}." init";
2	default	:PRE:	0	2	PerlHandler: 'My::Handler'
3	default	:PRE:	0	3	Key: 'k'
EOD

  foreach my $l (grep !/^\s*#/, split /\n/, $header) {
    $stmt->execute(split /\t+/, $l);
  }

  if( defined $data and length $data ) {
    foreach my $l (grep !/^\s*#/, split /\n/, $data) {
      $stmt->execute(split /\t+/, $l);
    }
  }

  $dbh->do('UPDATE cache SET v=v+1');
}

sub prepare_db {
  $dbh=DBI->connect( $db, $user, $pw,
		     {AutoCommit=>1, PrintError=>0, RaiseError=>1} )
    or die "ERROR: Cannot connect to $db: $DBI::errstr\n";

  $dbh->do($dbinit) if( length $dbinit );
  $dbh->do('DELETE FROM sequences');
  $dbh->do('DELETE FROM trans');
}

prepare_db;
sub n {my @c=caller; $c[1].'('.$c[2].'): '.$_[0];}

Apache::TestRequest::user_agent(reset => 1, requests_redirectable => 0);

######################################################################
## the real tests begin here                                        ##
######################################################################

# test key change during :PRE:
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." pre"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init pre', n 'init';

# test normal processing
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." /"
11	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." /uri"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init /', n '/';
ok t_cmp GET_BODY( '/uri' ), 'init /uri /', n '/uri';
ok t_cmp GET_BODY( '/uri/klaus' ), 'init /uri /', n '/uri/klaus';
ok t_cmp GET_BODY( '/klaus' ), 'init /', n '/klaus';

# conditional processing
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." 0_0"
11	k	:PRE:	0	1	Cond: 0
12	k	:PRE:	0	2	Do: $r->notes->{t}=$r->notes->{t}." 0_2"
13	k	:PRE:	1	0	Do: $r->notes->{t}=$r->notes->{t}." 1_0"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init 0_0 1_0', n 'Cond';

# loop back to :PRE: from PROC with a changed key
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." prek"
11	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." urik"
12	k	/	0	1	Key: 'k2'
13	k	/	0	2	State: 'PREPROC'
14	k	/u	0	0	Do: $r->notes->{t}=$r->notes->{t}." uk"
15	k	/u	0	1	Key: 'k2'
16	k	/u	0	2	State: 'PREPROC'
20	k2	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." prek2"
21	k2	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." urik2"
22	k2	/u	0	0	Do: $r->notes->{t}=$r->notes->{t}." uk2"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init prek urik prek2 urik2', n 'Key State';
ok t_cmp GET_BODY( '/u/1' ), 'init prek uk prek2 uk2 urik2', n 'Key State 2';

# 'Done' finishes current state
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." pre0"
11	k	:PRE:	0	1	Done
12	k	:PRE:	1	0	Do: $r->notes->{t}=$r->notes->{t}." pre1"
13	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." uri"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init pre0 uri', n 'Done';

# skip PROC state by jumping from PREPROC to DONE
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." pre0"
11	k	:PRE:	0	1	State: 'DONE'
12	k	:PRE:	0	2	Last
13	k	:PRE:	1	0	Do: $r->notes->{t}=$r->notes->{t}." pre1"
14	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." uri"
15	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." uri2"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init pre0', n 'State Last';
ok t_cmp GET_BODY( '/uri' ), 'init pre0', n 'State Last 2';

# skip PROC state by jumping from PREPROC to DONE but complete PREPROC first
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." pre0"
11	k	:PRE:	0	1	State: 'DONE'
12	k	:PRE:	1	0	Do: $r->notes->{t}=$r->notes->{t}." pre1"
13	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." uri"
14	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." uri2"
EOD
update_db;

ok t_cmp GET_BODY( '/' ), 'init pre0 pre1', n 'Last alone';
ok t_cmp GET_BODY( '/uri' ), 'init pre0 pre1', n 'Last alone 2';

# using 'Done' to prematurely finish PROC state
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." /"
11	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." /uri"
12	k	/uri	0	1	Done
EOD
update_db;

ok t_cmp GET_BODY( '/uri' ), 'init /uri', n 'Done';

# CALL
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." /:before"
11	k	/	0	1	Call: ':CALL:'
12	k	/	0	2	Do: $r->notes->{t}=$r->notes->{t}." /:after"
13	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." /uri:before"
14	k	/uri	0	1	Call: ':CALL:'
15	k	/uri	0	2	Do: $r->notes->{t}=$r->notes->{t}." /uri:after"
20	k	:CALL:	0	0	Do: $r->notes->{t}=$r->notes->{t}." c1"
21	k	:CALL:	0	1	Cond: 0
22	k	:CALL:	0	2	Do: $r->notes->{t}=$r->notes->{t}." c2"
23	k	:CALL:	1	0	Do: $r->notes->{t}=$r->notes->{t}." c3"
24	k	:CALL:	1	1	Do: $r->notes->{t}=$r->notes->{t}." c4"
EOD
update_db;

ok t_cmp GET_BODY( '/uri' ), 'init /uri:before c1 c3 c4 /uri:after /:before c1 c3 c4 /:after', n 'Call';

# LAST als Return in CALL & RESTART
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." /:before"
11	k	/	0	1	Call: ':CALL:'
12	k	/	0	2	Do: $r->notes->{t}=$r->notes->{t}." /:after"
13	k	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." /uri:before"
14	k	/uri	0	1	Call: ':CALL:'
15	k	/uri	0	2	Do: $r->notes->{t}=$r->notes->{t}." /uri:after"
20	k	:CALL:	0	0	Do: $r->notes->{t}=$r->notes->{t}." c1"
21	k	:CALL:	0	1	Last
22	k	:CALL:	0	2	Do: $r->notes->{t}=$r->notes->{t}." c2"
23	k	:CALL:	1	0	Do: $r->notes->{t}=$r->notes->{t}." c3"
24	k	:CALL:	1	1	Do: $r->notes->{t}=$r->notes->{t}." c4"
30	k	/rstrt	0	0	Do: $r->notes->{t}=$r->notes->{t}." /rstrt"
31	k	/rstrt	0	1	Restart: '/uri'
33	k	/rstrt	0	2	Do: $r->notes->{t}=$r->notes->{t}." must-not-occur"
33	k	/rstrt	1	0	Do: $r->notes->{t}=$r->notes->{t}." must-not-occur"
40	k	/rstrt2	0	0	Do: $r->notes->{t}=$r->notes->{t}." /rstrt2"
41	k	/rstrt2	0	1	Restart: '/uri', 'k2'
50	k2	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." init2";
51	k2	/	0	0	Do: $r->notes->{t}=$r->notes->{t}." /:before2"; $KEY='k';
52	k2	/	0	1	Call: ':CALL:'
53	k2	/	0	2	Do: $r->notes->{t}=$r->notes->{t}." /:after2"; $KEY='k2';
54	k2	/uri	0	0	Do: $r->notes->{t}=$r->notes->{t}." /uri:before2"; $KEY='k';
55	k2	/uri	0	1	Call: ':CALL:'
56	k2	/uri	0	2	Do: $r->notes->{t}=$r->notes->{t}." /uri:after2"; $KEY='k2';
EOD
update_db;

ok t_cmp GET_BODY( '/uri' ), 'init /uri:before c1 /uri:after /:before c1 /:after', n 'Last as return from Call';
ok t_cmp GET_BODY( '/rstrt' ), 'init /rstrt init /uri:before c1 /uri:after /:before c1 /:after', n 'Restart';
ok t_cmp GET_BODY( '/rstrt2' ), 'init /rstrt2 init2 /uri:before2 c1 /uri:after2 /:before2 c1 /:after2', n 'Restart 2';

# REDIRECT & ERROR
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	/	0	0	Redirect: 'http://'.join(':', $r->get_server_name, $r->get_server_port).'/redirect1'
11	k	/uri	0	0	Redirect: 'http://'.join(':', $r->get_server_name, $r->get_server_port).'/redirect2', 303
12	k	/error	0	0	Redirect: die "ERROR"
13	k	/404	0	0	Error: 404, 'this appears in the error_log'
EOD
update_db;

my $resp=GET '/';
ok t_cmp $resp->code, 302, n 'Redirect1: code';
ok t_cmp $resp->header('Location'), 'http://'.Apache::TestRequest::hostport.'/redirect1', n 'Redirect1: Location';

$resp=GET '/uri';
ok t_cmp $resp->code, 303, n 'Redirect2: code';
ok t_cmp $resp->header('Location'), 'http://'.Apache::TestRequest::hostport.'/redirect2', n 'Redirect2: Location';

t_client_log_error_is_expected(2);
$resp=GET '/error';
ok t_cmp $resp->code, 500, n 'Redirect error';

t_client_log_error_is_expected();
$resp=GET '/404';
ok t_cmp $resp->code, 404, n 'Error: 404';

# CLIENTIP convenience variable
$data=<<'EOD';
#id	xkey	xuri	xblock	xorder	xaction
10	k	:PRE:	0	0	Do: $r->notes->{t}=$r->notes->{t}." ".$CLIENTIP
EOD
update_db;

ok t_cmp GET_BODY( '/' ), qr/^init \d+\.\d+\.\d+\.\d+$/, n 'CLIENTIP';

$resp=OPTIONS '*';
ok t_cmp $resp->code, 200, n 'OPTIONS *';

$resp=OPTIONS '/';
ok t_cmp $resp->code, 200, n 'OPTIONS /';

t_client_log_error_is_expected();
{
  # this is not possible using Apache::TestRequest.

  use IO::Socket::INET ();
  my $hostport=Apache::TestRequest::hostport;
  my $s=IO::Socket::INET->new($hostport);
  die "ERROR: Cannot create socket: $!" unless( $s );
  $s->print("Options hallo HTTP/1.1\nHost: $hostport\nConnection: close\n\n");
  my $l=<$s>;
  ok $l=~/ 400 /, n 'OPTIONS hallo';
}

$dbh->disconnect;