use strict;
use warnings;
use utf8;

use Test::More tests => 2;
use Test::NoWarnings;
use Test::Differences;
use Test::Exception;
use Log::Any::Test;    # Must come before use Log::Any

use File::Basename qw( dirname );
use File::Slurp qw( read_file );
use File::Spec::Functions qw( catfile );
use Log::Any qw( $log );

subtest 'Everything but NoWarnings' => sub {

    use_ok( 'Zonemaster::Backend::Config' );

    subtest 'Set values' => sub {
        my $text = q{
            [DB]
            engine           = sqlite
            polling_interval = 1.5

            [MYSQL]
            host     = mysql-host
            port     = 3456
            user     = mysql_user
            password = mysql_password
            database = mysql_database

            [POSTGRESQL]
            host     = postgresql-host
            port     = 6543
            user     = postgresql_user
            password = postgresql_password
            database = postgresql_database

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [LANGUAGE]
            locale = sv_FI

            [PUBLIC PROFILES]
            default = /path/to/default.profile
            two     = /path/to/two.profile

            [PRIVATE PROFILES]
            three = /path/to/three.profile
            four  = /path/to/four.profile

            [ZONEMASTER]
            max_zonemaster_execution_time            = 1200
            number_of_processes_for_frontend_testing = 30
            number_of_processes_for_batch_testing    = 40
            lock_on_queue                            = 1
            age_reuse_previous_test                  = 800
        };
        my $config = Zonemaster::Backend::Config->parse( $text );
        isa_ok $config, 'Zonemaster::Backend::Config', 'parse() return value';
        is $config->DB_engine,            'SQLite',                    'set: DB.engine';
        is $config->DB_polling_interval,  1.5,                         'set: DB.polling_interval';
        is $config->MYSQL_host,           'mysql-host',                'set: MYSQL.host';
        is $config->MYSQL_port,           3456,                        'set: MYSQL.port';
        is $config->MYSQL_user,           'mysql_user',                'set: MYSQL.user';
        is $config->MYSQL_password,       'mysql_password',            'set: MYSQL.password';
        is $config->MYSQL_database,       'mysql_database',            'set: MYSQL.database';
        is $config->POSTGRESQL_host,      'postgresql-host',           'set: POSTGRESQL.host';
        is $config->POSTGRESQL_port,      6543,                        'set: POSTGRESQL.port';
        is $config->POSTGRESQL_user,      'postgresql_user',           'set: POSTGRESQL.user';
        is $config->POSTGRESQL_password,  'postgresql_password',       'set: POSTGRESQL.password';
        is $config->POSTGRESQL_database,  'postgresql_database',       'set: POSTGRESQL.database';
        is $config->SQLITE_database_file, '/var/db/zonemaster.sqlite', 'set: SQLITE.database_file';
        eq_or_diff { $config->LANGUAGE_locale }, { sv => 'sv_FI' }, 'set: LANGUAGE.locale';
        eq_or_diff { $config->PUBLIC_PROFILES }, {    #
            default => '/path/to/default.profile',
            two     => '/path/to/two.profile'
          },
          'set: PUBLIC PROFILES';
        eq_or_diff { $config->PRIVATE_PROFILES }, {    #
            three => '/path/to/three.profile',
            four  => '/path/to/four.profile'
          },
          'set: PRIVATE PROFILES';
        is $config->ZONEMASTER_max_zonemaster_execution_time,            1200, 'set: ZONEMASTER.max_zonemaster_execution_time';
        is $config->ZONEMASTER_number_of_processes_for_frontend_testing, 30,   'set: ZONEMASTER.number_of_processes_for_frontend_testing';
        is $config->ZONEMASTER_number_of_processes_for_batch_testing,    40,   'set: ZONEMASTER.number_of_processes_for_batch_testing';
        is $config->ZONEMASTER_lock_on_queue,                            1,    'set: ZONEMASTER.lock_on_queue';
        is $config->ZONEMASTER_age_reuse_previous_test,                  800,  'set: ZONEMASTER.age_reuse_previous_test';
    };

    subtest 'Default values' => sub {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite
        };
        my $config = Zonemaster::Backend::Config->parse( $text );
        cmp_ok abs( $config->DB_polling_interval - 0.5 ), '<', 0.000001, 'default: DB.polling_interval';
        is $config->MYSQL_port,      3306, 'default: MYSQL.port';
        is $config->POSTGRESQL_port, 5432, 'default: POSTGRESQL.port';
        eq_or_diff { $config->LANGUAGE_locale }, { en => 'en_US' }, 'default: LANGUAGE.locale';
        eq_or_diff { $config->PUBLIC_PROFILES }, { default => undef }, 'default: PUBLIC_PROFILES';
        eq_or_diff { $config->PRIVATE_PROFILES }, {}, 'default: PRIVATE_PROFILES';
        is $config->ZONEMASTER_max_zonemaster_execution_time,            600, 'default: ZONEMASTER.max_zonemaster_execution_time';
        is $config->ZONEMASTER_number_of_processes_for_frontend_testing, 20,  'default: ZONEMASTER.number_of_processes_for_frontend_testing';
        is $config->ZONEMASTER_number_of_processes_for_batch_testing,    20,  'default: ZONEMASTER.number_of_processes_for_batch_testing';
        is $config->ZONEMASTER_lock_on_queue,                            0,   'default: ZONEMASTER.lock_on_queue';
        is $config->ZONEMASTER_age_reuse_previous_test,                  600, 'default: ZONEMASTER.age_reuse_previous_test';

        is $config->RPCAPI_enable_add_api_user,  0,   'default: RPCAPI.enable_add_api_user';
        is $config->RPCAPI_enable_add_batch_job, 1,   'default: RPCAPI.enable_add_batch_job';
    };

    SKIP: {
        skip "no more deprecated values", 1;

        subtest 'Deprecated values and fallbacks that are unconditional' => sub {
            $log->clear();
            my $text = q{
                [DB]
                engine = SQLite
                [SQLITE]
                database_file = /var/db/zonemaster.sqlite
            };
            my $config = Zonemaster::Backend::Config->parse( $text );
        };
    }

    subtest 'Warnings' => sub {
        $log->clear();
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host     = localhost
            port     = 3333
            user     = mysql_user
            password = mysql_password
            database = mysql_database
        };
        my $config = Zonemaster::Backend::Config->parse( $text );
        $log->contains_ok( qr/MYSQL\.port.*MYSQL\.host/, 'warning: MYSQL.host is "localhost" and MYSQL.port defined' );
        is $config->MYSQL_host, 'localhost', 'set: MYSQL.host';
        is $config->MYSQL_port, 3333,        'set: MYSQL.port';
    };

    throws_ok {
        $log->clear();
        my $text = q{
            [DB]
            engine = SQLite
            [SQLITE]
            database_file = /var/db/zonemaster.sqlite
            [LANGUAGE]
            locale =
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/Use of empty LANGUAGE.locale property is not permitted/, 'die: Invalid empty locale tag';

    throws_ok {
        my $text = '{"this":"is","not":"a","valid":"ini","file":"!"}';
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/Failed to parse config/, 'die: Invalid INI format';

    throws_ok {
        my $text = q{
            [DB]
            engine = Excel

            [SQLITE]
            databse_file = /var/db/zonemaster.sqlite

            [ZNMEOTAESR]
            lock_on_queue = 1
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{section.*ZNMEOTAESR}, 'die: Invalid section name';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite
            pnlilog_iatnvrel = 0.5

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{property.*pnlilog_iatnvrel}, 'die: Invalid property name';

    throws_ok {
        my $text = q{
            [DB]
            engine = Excel
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/DB\.engine.*Excel/, 'die: Invalid DB.engine value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite
            polling_interval = hourly

            [SQLITE]
            databse_file = /var/db/zonemaster.sqlite
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{DB\.polling_interval.*hourly}, 'die: Invalid DB.polling_interval value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = 192.0.2.1:3306
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{MYSQL\.host.*192.0.2.1:3306}, 'die: Invalid MYSQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = Robert'); DROP TABLE Students;--
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{MYSQL\.user.*Robert'\); DROP TABLE Students;--}, 'die: Invalid MYSQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster
            password = (╯°□°)╯︵ ┻━┻
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{MYSQL\.password.*\(╯°□°\)╯︵ ┻━┻}, 'die: Invalid MYSQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
            database = |)/-\'|'/-\|3/-\$[-
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{MYSQL\.database.*|\)/-\'|'/-\\|3/-\\$[-}, 'die: Invalid MYSQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = 192.0.2.1:5432
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{POSTGRESQL\.host.*192.0.2.1:5432}, 'die: Invalid POSTGRESQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = Robert'); DROP TABLE Students;--
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{POSTGRESQL\.user.*Robert'\); DROP TABLE Students;--}, 'die: Invalid POSTGRESQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster
            password = (╯°□°)╯︵ ┻━┻
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{POSTGRESQL\.password.*\(╯°□°\)╯︵ ┻━┻}, 'die: Invalid POSTGRESQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
            database = |)/-\'|'/-\|3/-\$[-
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{POSTGRESQL\.database.*|\)/-\'|'/-\\|3/-\\$[-}, 'die: Invalid POSTGRESQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = ./relative/path/to/zonemaster.sqlite
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{SQLITE\.database_file.*\./relative/path/to/zonemaster.sqlite}, 'die: Invalid SQLITE.database_file value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [ZONEMASTER]
            max_zonemaster_execution_time = 0
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{ZONEMASTER\.max_zonemaster_execution_time.*0}, 'die: Invalid ZONEMASTER.max_zonemaster_execution_time value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [ZONEMASTER]
            lock_on_queue = -1
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{ZONEMASTER\.lock_on_queue.*-1}, 'die: Invalid ZONEMASTER.lock_on_queue value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [ZONEMASTER]
            number_of_processes_for_frontend_testing = 0
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{ZONEMASTER\.number_of_processes_for_frontend_testing.*0}, 'die: Invalid ZONEMASTER.number_of_processes_for_frontend_testing value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [ZONEMASTER]
            number_of_processes_for_batch_testing = 100000
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{ZONEMASTER\.number_of_processes_for_batch_testing.*100000}, 'die: Invalid ZONEMASTER.number_of_processes_for_batch_testing value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [ZONEMASTER]
            age_reuse_previous_test = 0
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr{ZONEMASTER\.age_reuse_previous_test.*0}, 'die: Invalid ZONEMASTER.age_reuse_previous_test value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.host/, 'die: Missing MYSQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.user/, 'die: Missing MYSQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster_user
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.password/, 'die: Missing MYSQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.database/, 'die: Missing MYSQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.host/, 'die: Missing POSTGRESQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.user/, 'die: Missing POSTGRESQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster_user
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.password/, 'die: Missing POSTGRESQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.database/, 'die: Missing POSTGRESQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.host/, 'die: Missing MYSQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.user/, 'die: Missing MYSQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster_user
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.password/, 'die: Missing MYSQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = MySQL

            [MYSQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/MYSQL\.database/, 'die: Missing MYSQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            user = zonemaster_user
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.host/, 'die: Missing POSTGRESQL.host value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            password = zonemaster_password
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.user/, 'die: Missing POSTGRESQL.user value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster_user
            database = zonemaster_database
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.password/, 'die: Missing POSTGRESQL.password value';

    throws_ok {
        my $text = q{
            [DB]
            engine = PostgreSQL

            [POSTGRESQL]
            host = zonemaster-host
            user = zonemaster_user
            password = zonemaster_password
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/POSTGRESQL\.database/, 'die: Missing POSTGRESQL.database value';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [LANGUAGE]
            locale = English
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/LANGUAGE\.locale.*English/, 'die: Invalid locale_tag in LANGUAGE.locale';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [LANGUAGE]
            locale = en_GB en_US
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/LANGUAGE\.locale.*en/, 'die: Repeated language code in LANGUAGE.locale';

    lives_and {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PUBLIC PROFILES]
            DEFAULT = /path/to/my.profile

            [PRIVATE PROFILES]
            SECRET = /path/to/my.profile
        };
        my $config = Zonemaster::Backend::Config->parse( $text );
        eq_or_diff { $config->PUBLIC_PROFILES },  { default => '/path/to/my.profile' }, 'normalize profile names under PUBLIC PROFILES';
        eq_or_diff { $config->PRIVATE_PROFILES }, { secret  => '/path/to/my.profile' }, 'normalize profile names under PRIVATE PROFILES';
    };

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PUBLIC PROFILES]
            -invalid-name- = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/PUBLIC PROFILES.*-invalid-name-/, 'die: Invalid profile name in PUBLIC PROFILES';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PRIVATE PROFILES]
            -invalid-name- = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/PRIVATE PROFILES.*-invalid-name-/, 'die: Invalid profile name in PRIVATE PROFILES';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PUBLIC PROFILES]
            valid-name = relative/path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/absolute.*valid-name/, 'die: Invalid absolute path in PUBLIC PROFILES';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PRIVATE PROFILES]
            valid-name = relative/path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/absolute.*valid-name/, 'die: Invalid absolute path in PRIVATE PROFILES';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PUBLIC PROFILES]
            valid-name = /path/to/my.profile
            valid-name = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/unique.*valid-name/, 'die: Repeated profile name in PUBLIC PROFILES section';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PRIVATE PROFILES]
            valid-name = /path/to/my.profile
            valid-name = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/unique.*valid-name/, 'die: Repeated profile name in PRIVATE PROFILES section';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PUBLIC PROFILES]
            pub-and-priv = /path/to/my.profile

            [PRIVATE PROFILES]
            pub-and-priv = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/unique.*pub-and-priv/, 'die: Repeated profile name across sections';

    throws_ok {
        my $text = q{
            [DB]
            engine = SQLite

            [SQLITE]
            database_file = /var/db/zonemaster.sqlite

            [PRIVATE PROFILES]
            default = /path/to/my.profile
        };
        Zonemaster::Backend::Config->parse( $text );
    }
    qr/PRIVATE PROFILES.*default/, 'die: Default profile in PRIVATE PROFILES';

    subtest 'RPCAPI experimental aliases' => sub {
        subtest 'default values' => sub {
            my $text = q{
                [DB]
                engine = SQLite

                [SQLITE]
                database_file = /var/db/zonemaster.sqlite
            };
            my $config = Zonemaster::Backend::Config->parse( $text );
            is $config->RPCAPI_enable_add_api_user,  0, 'default: RPCAPI.enable_add_api_user';
            is $config->RPCAPI_enable_add_batch_job, 1, 'default: RPCAPI.enable_add_batch_job';
            is $config->RPCAPI_enable_user_create,   0, 'default: RPCAPI.enable_user_create';
            is $config->RPCAPI_enable_batch_create,  1, 'default: RPCAPI.enable_batch_create';
        };

        subtest 'specifying stable and experimental parameters is forbidden' => sub {
            throws_ok {
                my $text = q{
                    [DB]
                    engine = SQLite

                    [SQLITE]
                    database_file = /var/db/zonemaster.sqlite

                    [RPCAPI]
                    enable_user_create = no
                    enable_add_api_user = yes
                };
                Zonemaster::Backend::Config->parse( $text );
            }
            qr/Error:.+RPCAPI\.enable_add_api_user.+RPCAPI\.enable_user_create/, 'die: RPCAPI stable and experimental alias (add_api_user/user_create)';

            throws_ok {
                my $text = q{
                    [DB]
                    engine = SQLite

                    [SQLITE]
                    database_file = /var/db/zonemaster.sqlite

                    [RPCAPI]
                    enable_add_batch_job = no
                    enable_batch_create = no
                };
                Zonemaster::Backend::Config->parse( $text );
            }
            qr/Error:.+RPCAPI\.enable_add_batch_job.+RPCAPI\.enable_batch_create/, 'die: RPCAPI stable and experimental alias (batch_job/batch_create)';
        };

        subtest 'setting alias' => sub {
            my $text = q{
                [DB]
                engine = SQLite

                [SQLITE]
                database_file = /var/db/zonemaster.sqlite

                [RPCAPI]
                enable_user_create = no
                enable_batch_create = no
            };
            my $config = Zonemaster::Backend::Config->parse( $text );
            is $config->RPCAPI_enable_user_create,   0, 'set: RPCAPI.enable_user_create';
            is $config->RPCAPI_enable_batch_create,  0, 'set: RPCAPI.enable_batch_create';
            is $config->RPCAPI_enable_add_api_user,  0, 'aliased: RPCAPI.enable_add_api_user';
            is $config->RPCAPI_enable_add_batch_job, 0, 'aliased: RPCAPI.enable_add_batch_job';
        };
    };

    {
        my $path = catfile( dirname( $0 ), '..', 'share', 'backend_config.ini' );
        my $text = read_file( $path );
        lives_ok {
            Zonemaster::Backend::Config->parse( $text );
        } 'default config is valid';
    }

};
