Database-BI
view release on metacpan or search on metacpan
t/transaction.t view on Meta::CPAN
# Phase 7 can verify it is forwarded from the user@host path component.
my $captured_sftp_user;
{
no strict 'refs';
no warnings 'redefine';
*{'Net::SFTP::Foreign::new'} = sub {
my ($class, $host, %opts) = @_;
return undef unless defined $host && $host eq 'mockhost';
$captured_sftp_user = $opts{user};
return bless { _stub_host => $host }, $class;
};
*{'Net::SFTP::Foreign::error'} = sub { '' };
*{'Net::SFTP::Foreign::get'} = sub {
my ($self, $remote, $local) = @_;
# Serve the CSV body for both the .csv and .log filenames so we
# can test that _sniff_data_ext renames the .log to .csv correctly.
return unless $remote =~ m{remote_sales\.(?:csv|log)$};
open(my $fh, '>', $local) or return;
print {$fh} $CSV_BODY;
close $fh;
};
}
# Phase 1: path-traversal attempt outside the /../ prefix is rejected.
$t->get_ok('/open?path=/' . url_escape('.') . '/../../etc/passwd')
->status_is(404, 'Phase 1: path-traversal outside /../ notation rejected');
# Phase 2: remote path whose file is not found on the mock host gives 404.
$t->get_ok('/open?path=' . url_escape('/../mockhost/tmp/file.php'))
->status_is(404, 'Phase 2: file not found on remote host gives 404');
# Phase 3: well-formed remote path with standard .csv extension opens correctly.
my $remote_path = '/../mockhost/tmp/remote_sales.csv';
$t->get_ok('/open?path=' . url_escape($remote_path))
->status_is(200, 'Phase 3: /../hostname/dir/file.csv returns 200')
->content_like(qr/Widget/, 'Phase 3: first data row visible')
->content_like(qr/Gadget/, 'Phase 3: second data row visible')
->content_like(qr/product/i, 'Phase 3: column header "product" present');
# Phase 4: spec-based access via _open_spec (used by join, combine, graph).
$t->get_ok('/api/columns?spec=' . url_escape('path:' . $remote_path))
->status_is(200, 'Phase 4: columns API resolves remote path spec')
->json_has('/columns', 'Phase 4: columns key present in JSON response');
# Phase 5: idempotency -- second request returns same columns.
$t->get_ok('/api/columns?spec=' . url_escape('path:' . $remote_path))
->status_is(200, 'Phase 5: second columns request is idempotent')
->json_has('/columns', 'Phase 5: columns key still present');
# Phase 6: non-standard extension (.log) -- _sniff_data_ext renames to .csv.
# The stub serves the same CSV body for .log; DataSource must detect the
# comma-separated content and open it correctly without a "Can't find a
# file" error.
my $log_path = '/../mockhost/tmp/remote_sales.log';
$t->get_ok('/open?path=' . url_escape($log_path))
->status_is(200, 'Phase 6: non-standard .log extension opens via content sniffing')
->content_like(qr/Widget/, 'Phase 6: first data row visible after sniff rename')
->content_like(qr/product/i, 'Phase 6: column header present after sniff rename');
# Phase 7: user@hostname syntax passes credentials to Net::SFTP::Foreign.
# DataSource splits "testuser@mockhost" into user="testuser", host="mockhost"
# and forwards user => $user to Net::SFTP::Foreign->new.
$captured_sftp_user = undef;
my $user_path = '/../testuser@mockhost/tmp/remote_sales.csv';
$t->get_ok('/open?path=' . url_escape($user_path))
->status_is(200, 'Phase 7: user@hostname syntax opens correctly')
->content_like(qr/Widget/, 'Phase 7: data visible with user@hostname path');
is($captured_sftp_user, 'testuser',
'Phase 7: username extracted from user@hostname and forwarded to Net::SFTP::Foreign');
# Tear down: restore original Net::SFTP::Foreign methods.
{
no strict 'refs';
no warnings 'redefine';
*{'Net::SFTP::Foreign::new'} = $orig_new if $orig_new;
*{'Net::SFTP::Foreign::error'} = $orig_error if $orig_error;
*{'Net::SFTP::Foreign::get'} = $orig_get if $orig_get;
}
};
subtest 'Transaction 40 -- Bar chart lifecycle' => sub {
plan tests => 37;
my $dir = tempdir(CLEANUP => 1);
my $csv = Mojo::File->new($dir)->child('bardata.csv')->to_string;
Mojo::File->new($csv)->spurt(
"tester,result,score\n"
. "Alice,pass,95\n"
. "Bob,fail,42\n"
. "Alice,pass,88\n"
. "Carol,pass,76\n"
. "Bob,pass,61\n"
);
my $base = '/bar?l=' . url_escape("path:$csv");
# Phase 1: count mode (no val param) -- count rows per category.
$t->get_ok($base . '&cat=tester')
->status_is(200, 'Phase 1: /bar count mode returns 200');
$t->content_like(qr/id="bar_chart"/, 'Phase 1: bar_chart SVG element present');
$t->content_like(qr/Count by tester/, 'Phase 1: title reflects count mode');
# Phase 2: value mode -- sum score per tester.
$t->get_ok($base . '&cat=tester&val=score')
->status_is(200, 'Phase 2: /bar value mode returns 200');
$t->content_like(qr/id="bar_chart"/, 'Phase 2: bar_chart SVG element present');
$t->content_like(qr/score by tester/, 'Phase 2: title reflects value mode');
# Phase 3: missing required cat param returns 400.
$t->get_ok('/bar?l=' . url_escape("path:$csv"))
->status_is(400, 'Phase 3: missing cat param returns 400');
# Phase 4: non-existent column returns 400.
$t->get_ok($base . '&cat=no_such_col')
->status_is(400, 'Phase 4: non-existent cat column returns 400');
# Phase 5: horizontal orientation.
$t->get_ok($base . '&cat=tester&orient=h')
->status_is(200, 'Phase 5: horizontal orientation returns 200');
$t->content_like(qr/id="bar_chart"/, 'Phase 5: bar chart SVG present in horizontal mode');
( run in 1.374 second using v1.01-cache-2.11-cpan-3f962061493 )