The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

package Poet::t::Conf;
$Poet::t::Conf::VERSION = '0.16';
use Poet::Tools qw(read_file tempdir_simple write_file);
use IPC::System::Simple qw(run);
use Test::Class::Most parent => 'Poet::Test::Class';
require Poet;
my $conf_files = {
'global/first.cfg' => { a => 10, b => 6, c => 11, d => 12 },
'global/second.cfg' => { e => 20 },
'global/third-not-a-config' => { f => 30 },
'layer/personal.cfg' => { c => 40, g => '_${b}', h => 51 },
'layer/development.cfg' => { f => 30 },
'local.cfg' => { h => '__${e}', layer => 'personal' },
};
my $expected_values = {
a => 10,
b => 6,
c => 40,
d => 12,
e => 20,
f => undef,
g => "_6",
h => "__20",
};
sub test_global : Tests {
my $self = shift;
my $poet = $self->temp_env( conf_files => $conf_files );
my $conf = $poet->conf();
while ( my ( $key, $value ) = each(%$expected_values) ) {
is( $conf->get($key), $value, "$key = " . ( defined($value) ? $value : 'undef' ) );
}
}
sub test_duplicate : Tests {
my $self = shift;
throws_ok(
sub {
$self->temp_env(
conf_files => { %$conf_files, 'global/fourth.cfg' => { j => 71, c => 72 } } );
},
qr/key 'c' defined in both '.*(first|fourth)\.cfg' and '.*(first|fourth)\.cfg'/,
'cannot define same key in multiple global config files'
);
}
sub test_set_local : Tests {
my $self = shift;
my $poet = $self->temp_env( conf_files => { 'global/foo.cfg' => { a => 5, b => 6, c => 7 } } );
my $conf = $poet->conf();
is( $conf->get('a'), 5, 'a = 5' );
is( $conf->get( 'b' => 0 ), 6, 'b = 6' );
is( $conf->get('c'), 7, 'c = 7' );
{
my $lex = $conf->set_local( { a => 15, c => 17 } );
is( $conf->get('a'), 15, 'a = 15 (set_local)' );
is( $conf->get('b'), 6, 'b = 6 (set_local)' );
is( $conf->get('c'), 17, 'c = 17 (set_local)' );
{
my $lex = $conf->set_local( { c => 27 } );
is( $conf->get('c'), 27, 'c = 27 (set_local depth 2)' );
my $lex2 = $conf->set_local( { c => 37 } );
$lex2 = 'shiny';
is( $conf->get('c'), 27, 'c = 27 (did not hold on to lexical)' );
}
is( $conf->get('c'), 17, 'c = 17 (set_local)' );
}
is( $conf->get('a'), 5, 'a = 5 (restored)' );
is( $conf->get('b'), 6, 'b = 6 (restored)' );
is( $conf->get('c'), 7, 'c = 7 (restored)' );
}
sub test_dot_notation : Tests {
my $self = shift;
my $conf_files = {
'layer/personal.cfg' => '
a:
b:
c: 1
d: 2
e:
f:
g: 3
h: 4
',
'local.cfg' => '
layer: personal
a.b.c: 6
e:
f:
g: 7
'
};
my $poet = $self->temp_env( conf_files => $conf_files );
my %expected_values = (
'a' => { 'b' => { c => 6, d => 2 } },
'a.b' => { c => 6, d => 2 },
'a.b.c' => 6,
'a.b.d' => 2,
'e' => { f => { g => 7 } },
'e.f' => { g => 7 },
'e.f.g' => 7,
'x.y.z' => undef
);
my $conf = $poet->conf();
my $check_expected = sub {
my $desc = shift;
foreach my $key ( sort( keys(%expected_values) ) ) {
my $value = $expected_values{$key};
cmp_deeply( $conf->get($key), $value, "$key - $desc" );
}
};
$check_expected->('initial');
throws_ok( sub { $conf->get('a.b.c.z') },
qr/hash value expected for conf key 'a.b.c', got non-hash '6'/, "a.b.c.z" );
$conf_files->{'layer/personal.cfg'} = { 'a' => { 'b' => 17 } };
throws_ok(
sub { $poet = $self->temp_env( conf_files => $conf_files ) },
qr/error assigning to 'a.b.c' in .*; 'a.b' already has non-hash value/,
"e.f: 17"
);
{
my $lex = $conf->set_local( { 'a.b.c' => 16 } );
local $expected_values{'a'} = { 'b' => { c => 16, d => 2 } },
local $expected_values{'a.b'} = { c => 16, d => 2 };
local $expected_values{'a.b.c'} = 16;
$check_expected->('set_local');
}
$check_expected->('after set_local');
}
sub test_types : Tests {
my $self = shift;
my $truth = { c => 't', d => 'true', e => 'y', f => 'yes' };
my $falsity = { c => 'f', d => 'false', e => 'n', f => 'no' };
my $conf_files = {
'global.cfg' => {
scalar => 5,
list => [ 1, 2, 3 ],
hash => { size => 'large', flavor => 'chocolate' },
truth => $truth,
falsity => $falsity,
}
};
my $poet = $self->temp_env( conf_files => $conf_files );
my $conf = $poet->conf();
cmp_deeply( $conf->get_list('list'), [ 1, 2, 3 ], 'list ok' );
foreach my $key (qw(scalar hash)) {
throws_ok( sub { $conf->get_list($key) }, qr/list value expected/ );
}
cmp_deeply( $conf->get_hash('hash'), { size => 'large', flavor => 'chocolate' }, 'hash ok' );
foreach my $key (qw(scalar list)) {
throws_ok( sub { $conf->get_hash($key) }, qr/hash value expected/ );
}
foreach my $key (qw(c d e f)) {
is( $conf->get_boolean("truth.$key"), 1, "$key = true" );
is( $conf->get_boolean("falsity.$key"), 0, "$key = false" );
}
foreach my $key (qw(scalar list hash)) {
throws_ok( sub { $conf->get_boolean($key) }, qr/boolean value expected/ );
}
}
sub test_layer_required : Tests {
my $self = shift;
throws_ok(
sub { $self->temp_env( conf_files => { 'local.cfg' => {} } ) },
qr/must specify layer/,
'no layer'
);
}
sub test_interpolation : Tests {
my $self = shift;
my $poet = $self->temp_env(
conf => {
layer => 'development',
'a.b' => 'bar',
'c' => '/foo/${a.b}/baz',
'd' => '/foo/${huh}/baz',
'deep' => { 'e' => 5, 'f' => [ '${c}', ['${a.b}'] ] }
}
);
my $conf = $poet->conf();
is( $conf->get('c'), '/foo/bar/baz', 'substitution' );
throws_ok { $conf->get('d') } qr/could not get conf for 'huh'/, 'bad substitution';
cmp_deeply(
$conf->get('deep'),
{
e => 5,
f => [ '/foo/bar/baz', ['bar'] ]
},
'deep'
);
}
sub test_dynamic_conf : Tests {
my $self = shift;
my $poet = $self->temp_env();
write_file( $poet->conf_path("dynamic/foo.mc"), "<% 2+2 %>" );
ok( !-d $poet->data_path("conf/dynamic"), "data/conf/dynamic does not exist" );
run( $poet->conf_path("dynamic/gen.pl") );
ok( -d $poet->data_path("conf/dynamic"), "data/conf/dynamic exists" );
ok( -f $poet->data_path("conf/dynamic/foo"), "conf/dynamic/foo exists" );
is( read_file( $poet->data_path("conf/dynamic/foo") ), 4, "foo has correct content" );
ok( !-f $poet->data_path("conf/dynamic/gen.pl"), "conf/dynamic/gen.pl does not exist" );
}
sub test_get_secure : Tests {
my $self = shift;
my $tempdir = tempdir_simple('poet-conf-XXXX');
my $secure_file = "$tempdir/supersecret.cfg";
write_file( $secure_file, "foo: 7\nbar: 8\n" );
my $poet = $self->temp_env(
conf_files => {
'local.cfg' => {
layer => 'development',
'foo' => 0,
'baz' => 9,
'conf.secure_conf_file' => $secure_file
}
}
);
my $conf = $poet->conf;
is( $conf->get_secure('foo'), 7, "foo=0" );
is( $conf->get_secure('bar'), 8, "bar=8" );
is( $conf->get_secure('baz'), 9, "baz=9" );
is( $conf->get_secure('blargh'), undef, "blargh=undef" );
is( $conf->get('bar'), undef, "bar=undef" );
ok( ( !grep { /bar/ } $conf->get_keys() ), "no bar in keys" );
}
1;