# https://github.com/PerlDancer/Dancer2/blob/docs/doc-rewrite-grant/lib/Dancer2/Manual/Tutorial.pod # run: carmel exec prove -lv t/household.t use 5.34.0; # say BEGIN { # set test env otherwise development config settings loaded - unless explicitly # set at command-line: "export DANCER_ENVIRONMENT=development; prove -lv t/" $ENV{DANCER_ENVIRONMENT} ||= $ENV{PLACK_ENV} ||= 'test'; } use File::Path 'remove_tree'; # say "$_: $INC{$_}" for sort keys %INC; use File::Spec::Functions; # catfile use Path::Tiny qw(tempfile); use HTTP::Request::Common; use Data::Printer; use HTTP::Cookies; use Plack::Test; use HTML::Form; use Test::More; use App::Test; # get_next_location, process_request & initialise use_ok('DocsLib'); my $test = Plack::Test->create( DocsLib->to_app ); # p $test->app; exit; my $jar = HTTP::Cookies->new(); my $cfg = DocsLib->dancer_app->settings; # p $cfg; exit; my $url = 'http://localhost/dpw'; my $userid = $cfg->{user}->{name}; my $passwd = $cfg->{user}->{plain_text_pwd} || die qq!require 'plain_text_pwd' setting in test_local.yml file!; my %hx_request_headers = ( HX_Target => 'content-div', # ← optional: element id to swap HX_Trigger => 'save-button', # ← optional: id/name of the triggering element HX_Request => 'true', # ← required by htmx HX_Current_URL => '/', # necessary for correct url in record.tt using hx-current-url header ); App::Test::initialise( $jar, $test ); # check we have test env config, or die: die "####### incorrect config loaded ########" unless $cfg->{environment} eq 'test'; { # clear test-files dir 1st: my $docs_path = $cfg->{documents_path}; # say $docs_path; die '####### '.$docs_path.' upload path does not exist' unless -e $docs_path; die "####### incorrect docs-path ########" unless $docs_path =~ m!t/file-tree!; remove_tree( catfile($docs_path,'household'), { keep_root => 1, error => \my $err } ); die Dumper $err if @$err; } # use 'our' to allow temporary dynamic block substitution our %entry = ( description => 'Unident Bearcat scanner', # deliberate spelling error, corrected later filename => 'test_one.txt', category => 'domestic', retained => 'n', comment => 'E-bay via PayPal', date => '2011-03-06', ); subtest 'Landing page' => sub { my $res = $test->request( GET '/dpw/' ); ok( $res->is_redirect, '[GET /] redirect' ); }; subtest 'Login' => sub { my @fields = ( username => $userid, password => $passwd, ); my $request = POST '/login', \@fields ; my $response = $test->request( $request ); ok( $response->is_redirect, '[POST /login] redirect' ); # handle cookies for next request: $jar->extract_cookies($response); }; subtest 'Home page' => sub { # create HTTP::Request object and add cookies to it: my $req = GET $url . '/', %hx_request_headers; $jar->add_cookie_header( $req ); # p $req->dump; # resubmit GET '/', should get 'new_document' link: my $res = $test->request( $req ); # p $res; like( $res->as_string, qr{/dpw/new_document}, 'have link for new document' ); }; subtest 'Create document' => sub { my $req = do { my $temp = tempfile(); $temp->spew("Test file content\n"); # Path::Tiny # need to temporarily substitute $entry{filename} with array including content-type: local $entry{filename} = [ $temp->stringify, $entry{filename}, 'text/plain' ]; POST $url . '/create', Content_Type => 'form-data', Content => \%entry, %hx_request_headers; }; my $res = process_request($req); # successful input gets redirect ok( $res->is_redirect, '[POST /create] redirect' ); # p %entry; # have to retrieve update manually due to redirect: my $next = get_next_location($res); # say $next->as_string; like( $next->as_string, qr/Input success/, 'has input success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => '$entry{$_}']" ) for keys %entry; my $txt = App::Test::get_records_count($url); # have to get '/' for footer like( $txt, qr/Total records 1/, 'expected number of records' ); my $path = catfile($cfg->{documents_path},'household', 'domestic', $entry{filename}); # say $path; ok( -e $path, 'OK: expected path to uploaded file'); }; subtest 'Edit document' => sub { my $req = GET $url . '/edit/1', # full url to match cookie expectation %hx_request_headers; # p $req; $jar->add_cookie_header( $req ); my $res = $test->request( $req ); # p $res; like( $res->as_string, qr{/update/1}, 'contains update link' ); like( $res->as_string, qr/Unident/, 'has expected content [Unident]' ); }; subtest 'Update document' => sub { $entry{description} = 'Uniden Bearcat scanner'; my $req = POST $url . '/update/1', \%entry, %hx_request_headers; # p %entry; my $res = process_request($req); # successful input gets redirect ok( $res->is_redirect, '[POST /update] redirect' ); # have to retrieve update manually due to redirect: my $next = get_next_location($res); like( $next->as_string, qr/Update success/, 'has update success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => '$entry{$_}']" ) for keys %entry; # original text not found: unlike( $next->as_string, qr/Unident/, 'lacks original content [Unident]' ); }; subtest 'Edit file' => sub { note "STEP 1 — fetch the edit form"; my $edit_form_res = do { my $req = GET $url . '/edit/1?replace_file=1', %hx_request_headers; process_request($req); }; ok($edit_form_res->is_success, 'Fetched edit form'); note "STEP 2 — parse the form"; my $form = HTML::Form->parse($edit_form_res); # p $form; isa_ok( $form, 'HTML::Form' ); # note "Form action: " . $form->action; # note "Form method: " . $form->method; note "STEP 3 — modify the values you want"; $entry{filename} = 'test_two.txt'; # change filename for global %entry # create temp file: my $temp = tempfile(); $temp->spew("Test file content\n"); # Path::Tiny { local $entry{filename} = [ $temp->stringify, $entry{filename}, 'text/plain' ]; # p %entry; $form->value( $_ => $entry{$_} ) for keys %entry; } note "STEP 4 — submit to the hx-post endpoint (or form action)"; $form->method('POST'); # force POST for submission my $update_form_req = $form->make_request; # --- IMPORTANT: detect hx-post & override URL if hx-post exists --- my $hx_post = $form->attr('hx-post'); note "Detected hx-post: $hx_post"; $update_form_req->uri($url.$hx_post) if $hx_post; # or get 404 not found note "STEP 5 — attach htmx headers"; $update_form_req->header(%hx_request_headers); note "STEP 6 — attach cookies + send request"; my $update_form_res = process_request($update_form_req); # p $update_form_res; ok( $update_form_res->is_redirect, '[POST /create] redirect' ); # have to retrieve update manually due to redirect: my $next = get_next_location($update_form_res); # say $next->as_string; like( $next->as_string, qr/Update success/, 'has update success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => '$entry{$_}']" ) for keys %entry; unlike( $next->as_string, qr/test_one.txt/, 'lacks original filename' ); my $txt = App::Test::get_records_count($url); # have to get '/' for footer like( $txt, qr/Total records 1/, 'expected number of records' ); my $docs_path = catfile( $cfg->{documents_path},'household', 'domestic' ); my $new_file = catfile( $docs_path, 'test_two.txt' ); # say $new_file; my $old_file = catfile( $docs_path, 'test_one.txt' ); # say $old_file; ok( -e $new_file, 'OK: expected path to new file'); ok(! -e $old_file, 'OK: expected path to old file empty'); }; subtest 'Update document without file' => sub { # get filepath before deletion: my $path = catfile($cfg->{documents_path},'household', 'domestic', $entry{filename}); say $path; local $entry{filename} = undef; my $req = POST $url . '/update/1', \%entry, %hx_request_headers; # p %entry; my $res = process_request($req); # successful input gets redirect ok( $res->is_redirect, '[POST /update] redirect' ); # have to retrieve update manually due to redirect: my $next = get_next_location($res); like( $next->as_string, qr/Update success/, 'has update success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => '$entry{$_}']" ) for keys %entry; # original file not found: ok(! -e $path, 'OK: expected path to old file empty'); }; subtest 'Create new document without file' => sub { local $entry{filename} = undef; my $req = POST $url . '/create', Content_Type => 'form-data', Content => \%entry, %hx_request_headers; my $res = process_request($req); # successful input gets redirect ok( $res->is_redirect, '[POST /create] redirect' ); # p %entry; # have to retrieve update manually due to redirect: my $next = get_next_location($res); # say $next->as_string; like( $next->as_string, qr/Input success/, 'has input success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => $entry{$_}]" ) for keys %entry; my $txt = App::Test::get_records_count($url); # have to get '/' for footer like( $txt, qr/Total records 2/, 'expected number of records' ); }; subtest 'Add file to new document' => sub { # based on 'Edit file' subtest my $edit_form_res = do { my $req = GET $url . '/edit/2', %hx_request_headers; process_request($req); }; ok($edit_form_res->is_success, 'Fetched edit form'); my $form = HTML::Form->parse($edit_form_res); # p $form; isa_ok( $form, 'HTML::Form' ); $entry{filename} = 'test_three.txt'; # create temp file: my $temp = tempfile(); $temp->spew("Test file content\n"); # Path::Tiny { local $entry{filename} = [ $temp->stringify, $entry{filename}, 'text/plain' ]; # p %entry; $form->value( $_ => $entry{$_} ) for keys %entry; } $form->method('POST'); # force POST for submission my $update_form_req = $form->make_request; # --- IMPORTANT: detect hx-post & override URL if hx-post exists --- my $hx_post = $form->attr('hx-post'); $update_form_req->uri($url.$hx_post) if $hx_post; # or get 404 not found $update_form_req->header(%hx_request_headers); my $update_form_res = process_request($update_form_req); # p $update_form_res; ok( $update_form_res->is_redirect, '[POST /create] redirect' ); # have to retrieve update manually due to redirect: my $next = get_next_location($update_form_res); # say $next->as_string; like( $next->as_string, qr/Update success/, 'has update success' ); like( $next->as_string, qr/$entry{$_}/, "have expected content [$_ => '$entry{$_}']" ) for keys %entry; my $txt = App::Test::get_records_count($url); # have to get '/' for footer like( $txt, qr/Total records 2/, 'expected number of records' ); my $filepath = catfile( $cfg->{documents_path},'household', 'domestic', $entry{filename} ); # say $new_file; ok( -e $filepath, 'OK: expected path to new file'); }; subtest 'Search' => sub { my $req = POST $url . '/search' , [ search => 'scanner' ], %hx_request_headers; # don't need to add cookies here as route isn't protected by login my $res = $test->request( $req ); # p $res; like( $res->as_string, qr/scanner/, 'search by free-text returned search term' ); }; subtest 'Test error' => sub { my %data = (title => 'title', content => 'content', test_err => 'test error'); my $req = POST $url . '/create', \%data, %hx_request_headers; $jar->add_cookie_header( $req ); my $res = $test->request( $req ); # p $res; like( $res->as_string, qr/test error/, 'has error string' ); }; subtest 'Invalid Download' => sub { my @tests = ( { id => '', error => 'Resource not found' }, # custom 404 page { id => '.', error => 'Invalid file name' }, { id => '..', error => 'Invalid file name' }, { id => '../etc/passwd', error => 'Resource not found' }, # custom 404 page { id => 'nonexist.txt', error => 'Resource not found' }, ); for my $t (@tests) { my $req = GET $url . '/download/domestic/'.$t->{id}, %hx_request_headers; $jar->add_cookie_header( $req ); my $res = $test->request( $req ); # p $res; like( $res->as_string, qr/$t->{error}/, qq!has expected error for "$t->{id}"! ); } }; done_testing(14);