Database-BI
view release on metacpan or search on metacpan
# 127.x.x.x -> 0 [C]
# 0.0.0.0 -> 0 [D]
# ::1 -> 0 [E]
# Hostname (not IP): return 1 -> 1 [F]
# Dotted-quad, inet_aton fails -> 1 [G]
# Private ranges:
# 10/8 -> 0 [H]
# 172.16/12 -> 0 [I]
# 192.168/16 -> 0 [J]
# 169.254/16 (link-local) -> 0 [K]
# 100.64/10 (CGNAT) -> 0 [L]
# Public IP (8.8.8.8) -> 1 [M]
# ======================================================================
subtest '_is_safe_url path-A: no https?:// protocol -> 0' => sub {
ok !$IS_SAFE->('ftp://example.com'), 'path A: ftp:// is not safe (blocked)';
};
subtest '_is_safe_url path-B: localhost -> 0' => sub {
ok !$IS_SAFE->('http://localhost/api'), 'path B: localhost blocked';
};
subtest '_is_safe_url path-C: 127.x.x.x -> 0' => sub {
ok !$IS_SAFE->('http://127.0.0.1/'), 'path C: 127.0.0.1 blocked';
ok !$IS_SAFE->('http://127.255.255.255/'), 'path C: 127.x.x.x upper bound blocked';
};
subtest '_is_safe_url path-D: 0.0.0.0 -> 0' => sub {
ok !$IS_SAFE->('http://0.0.0.0/'), 'path D: 0.0.0.0 blocked';
};
subtest '_is_safe_url path-E: ::1 (IPv6 loopback) -> 0' => sub {
ok !$IS_SAFE->('http://::1/'), 'path E: ::1 blocked';
};
subtest '_is_safe_url path-F: hostname (not IP) -> 1 (allowed)' => sub {
ok $IS_SAFE->('https://example.com/data.csv'), 'path F: public hostname allowed';
};
subtest '_is_safe_url path-G: dotted-quad but invalid octet -> inet_aton undef -> 1' => sub {
# 256.0.0.1 passes the dotted-quad regex but inet_aton returns undef.
# The "or return 1" guard handles this; the IP cannot be checked against ranges.
ok $IS_SAFE->('http://256.0.0.1/'), 'path G: invalid octet -> inet_aton undef -> pass';
};
subtest '_is_safe_url path-H: 10.x.x.x (RFC 1918) -> 0' => sub {
ok !$IS_SAFE->('http://10.0.0.1/'), 'path H: 10.0.0.1 blocked';
ok !$IS_SAFE->('http://10.255.255.254/'), 'path H: 10.255.255.254 blocked';
};
subtest '_is_safe_url path-I: 172.16-31.x.x (RFC 1918) -> 0' => sub {
ok !$IS_SAFE->('http://172.16.0.1/'), 'path I: 172.16.x.x blocked';
ok !$IS_SAFE->('http://172.31.255.254/'), 'path I: 172.31.x.x upper bound blocked';
};
subtest '_is_safe_url path-J: 192.168.x.x (RFC 1918) -> 0' => sub {
ok !$IS_SAFE->('http://192.168.0.1/'), 'path J: 192.168.x.x blocked';
};
subtest '_is_safe_url path-K: 169.254.x.x (link-local / metadata) -> 0' => sub {
ok !$IS_SAFE->('http://169.254.169.254/latest/meta-data/'), 'path K: AWS metadata endpoint blocked';
};
subtest '_is_safe_url path-L: 100.64.x.x (CGNAT / Tailscale) -> 0' => sub {
ok !$IS_SAFE->('http://100.64.0.1/'), 'path L: CGNAT range blocked';
};
subtest '_is_safe_url path-M: public IP 8.8.8.8 -> 1 (no private range matches)' => sub {
ok $IS_SAFE->('http://8.8.8.8/'), 'path M: public IP 8.8.8.8 allowed';
};
# ======================================================================
# PATH COVERAGE: Dashboard::_get_columns
#
# CFG:
# A: source->columns returns arrayref -> return it directly
# B: columns undef, no records -> return ()
# C: columns undef, records, id_col in data -> id_col first
# D: columns undef, records, id_col absent from data -> alphabetical
# ======================================================================
{
# Helper: build a fake DataSource object with controlled columns/id_column.
sub fake_source {
my ($cols, $id_col) = @_;
return bless { _columns => $cols, _id_col => $id_col },
'Database::BI::Model::DataSource';
}
subtest '_get_columns path-A: source->columns returns arrayref -> returned' => sub {
my $src = fake_source([qw(product amount region)], 'product');
my @result = $GET_COLS->($src, []);
is_deeply \@result, [qw(product amount region)], 'path A: columns arrayref returned as-is';
};
subtest '_get_columns path-B: columns undef, no records -> empty list' => sub {
my $src = fake_source(undef, 'id');
my @result = $GET_COLS->($src, []);
is_deeply \@result, [], 'path B: no columns + no records -> ()';
};
subtest '_get_columns path-C: columns undef, id_col present in data -> id_col first' => sub {
my $src = fake_source(undef, 'id');
my @result = $GET_COLS->($src, [{ id => '1', amount => '10', region => 'N' }]);
is $result[0], 'id', 'path C: id_column appears first';
is_deeply [sort @result[1..$#result]], [qw(amount region)], 'path C: rest sorted alphabetically';
};
subtest '_get_columns path-D: columns undef, id_col absent from data -> alphabetical' => sub {
my $src = fake_source(undef, 'entry'); # 'entry' not in data
my @result = $GET_COLS->($src, [{ amount => '10', region => 'N' }]);
is $result[0], 'amount', 'path D: alphabetical first col (amount) used as id';
is $result[1], 'region', 'path D: second col follows';
};
}
# ======================================================================
# PATH COVERAGE: Dashboard::_spec_to_url
#
# CFG (cascading if/elsif with immediate returns):
# A: table: spec -> /view/<name>
( run in 2.281 seconds using v1.01-cache-2.11-cpan-9789f410c06 )