Database-BI
view release on metacpan or search on metacpan
t/transaction.t view on Meta::CPAN
# Phase 4: filter on a TSV column.
$t->get_ok('/view/employees?f=' . url_escape('Department:eq:Engineering'))
->status_is(200, 'Phase 4: filtered /view returns 200');
$t->content_like(qr/Alice/, 'Phase 4: filter keeps matching row');
$t->content_unlike(qr/Marketing/, 'Phase 4: filter removes non-matching row');
# Phase 5: export to CSV.
$t->get_ok('/export?l=' . url_escape('table:employees') . '&format=csv')
->status_is(200, 'Phase 5: CSV export returns 200')
->content_type_like(qr{text/csv}, 'Phase 5: content-type is text/csv')
->content_like(qr/Alice/, 'Phase 5: exported CSV contains data');
# Phase 6: export to SQLite (requires DBD::SQLite).
SKIP: {
eval { DBI->install_driver('SQLite') }
or skip 'DBD::SQLite not available for SQLite export', 2;
$t->get_ok('/export?l=' . url_escape('table:employees') . '&format=sqlite')
->status_is(200, 'Phase 6: SQLite export returns 200')
->content_type_like(qr{sqlite}, 'Phase 6: content-type contains sqlite');
}
}
# Phase 7: upload a TSV file and open it (always run if upload route works).
SKIP: {
my $tsv_body = "item\tqty\tprice\nWidget\t10\t4.99\nGadget\t5\t9.99\n";
$t->post_ok('/upload',
form => { file => { content => $tsv_body, filename => 'tmpparts.tsv' } }
)->status_is(200, 'Phase 7: TSV upload returns 200');
t/transaction.t view on Meta::CPAN
# Phase 4: filter works on .sqlite3 source.
$t->get_ok('/open?path=' . url_escape($db_path) . '&f=' . url_escape('name:eq:Sprocket'))
->status_is(200, 'Phase 4: filtered /open returns 200');
$t->content_like(qr/Sprocket/, 'Phase 4: filter keeps matching row');
$t->content_unlike(qr/Flywheel/, 'Phase 4: filter removes non-matching row');
# Phase 5: CSV export of a .sqlite3 source.
$t->get_ok('/export?l=' . url_escape("path:$db_path") . '&format=csv')
->status_is(200, 'Phase 5: CSV export of .sqlite3 returns 200')
->content_type_like(qr{text/csv}, 'Phase 5: content-type is text/csv');
# Phase 6: browse directory shows the .sqlite3 file.
$t->get_ok('/browse?path=' . url_escape($dir))
->status_is(200, 'Phase 6: browse dir returns 200');
$t->content_like(qr/widgets\.sqlite3/, 'Phase 6: .sqlite3 file listed in browser');
# Phase 7: idempotency -- second request hits the cache.
$t->get_ok('/open?path=' . url_escape($db_path))
->status_is(200, 'Phase 7: second /open is idempotent');
t/transaction.t view on Meta::CPAN
# Phase 4: filter works on .sqlite source.
$t->get_ok('/open?path=' . url_escape($db_path) . '&f=' . url_escape('sensor:eq:Alpha'))
->status_is(200, 'Phase 4: filtered /open returns 200');
$t->content_like(qr/Alpha/, 'Phase 4: filter keeps matching row');
$t->content_unlike(qr/Beta/, 'Phase 4: filter removes non-matching row');
# Phase 5: CSV export of a .sqlite source.
$t->get_ok('/export?l=' . url_escape("path:$db_path") . '&format=csv')
->status_is(200, 'Phase 5: CSV export of .sqlite returns 200')
->content_type_like(qr{text/csv}, 'Phase 5: content-type is text/csv');
# Phase 6: browse directory shows the .sqlite file.
$t->get_ok('/browse?path=' . url_escape($dir))
->status_is(200, 'Phase 6: browse dir returns 200');
$t->content_like(qr/readings\.sqlite/, 'Phase 6: .sqlite file listed in browser');
# Phase 7: idempotency -- second request hits the cache.
$t->get_ok('/open?path=' . url_escape($db_path))
->status_is(200, 'Phase 7: second /open is idempotent');
}
delete $ledger{'DataSource.fetch_all.ok'};
};
# ---------------------------------------------------------------------------
# HTTP API tests via Test::Mojo
# ---------------------------------------------------------------------------
# --- GET / ----------------------------------------------------------------
subtest 'GET / -- home page lists available tables' => sub {
# Status and content-type smoke test.
$t->get_ok('/')->status_is(200)->content_type_like(qr{text/html})
# The Browse card must contain a path text input (id="bi-path-input")
# and its form must target /open. These assertions exist specifically
# to catch the regression where the path input was silently removed
# from home.html.tt without any test failing.
->content_like(qr/id="bi-path-input"/, 'home page has path input field')
->content_like(qr/action="\/open"/, 'Browse card form targets /open')
# The layout writes the resolved language into <html lang>. index() rendered
# its template without passing it, so the attribute was empty here while the
# tutorial's page carried the language - a screen reader and a translation
delete $ledger{'GET./combine.404'};
};
# --- GET /export?format=json ----------------------------------------------
subtest 'GET /export?l=table:sales&format=json -- 200 application/json' => sub {
# The JSON export format is documented in the POD alongside csv and sqlite.
# Verify it produces a JSON content type and a non-empty body.
$t->get_ok("/export?l=table:$SALES_TABLE&format=json")
->status_is(200, 'JSON export returns 200')
->content_type_like(qr{application/json}, 'content-type is JSON');
delete $ledger{'GET./export.json'};
};
# --- GET /graph error paths -----------------------------------------------
subtest 'GET /graph (no x= or y=) -- 400 Missing x or y column parameter' => sub {
# graph_view checks for both x= and y= before opening the data source.
# Missing either produces an immediate 400 with a plain-text error.
$t->get_ok("/graph?l=table:$SALES_TABLE&x=product")
->status_is(400, '400 when y= is absent')
( run in 1.648 second using v1.01-cache-2.11-cpan-99fa8243a42 )