You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/test/tap/tests/unit/config_write_unit-t.cpp

771 lines
30 KiB

/**
* @file config_write_unit-t.cpp
* @brief Unit tests for ProxySQL_Config Write_*_to_configfile() functions.
*
* Tests the following functions:
* - Write_MySQL_Users_to_configfile()
* - Write_Scheduler_to_configfile()
* - Write_Restapi_to_configfile()
* - Write_MySQL_Query_Rules_to_configfile()
* - Write_MySQL_Servers_to_configfile()
* - Write_Global_Variables_to_configfile()
* - Write_ProxySQL_Servers_to_configfile()
*
* Each test creates an in-memory SQLite3DB, populates it with the
* appropriate table schema and test data, constructs a ProxySQL_Config,
* and verifies the formatted output string.
*/
#include "tap.h"
#include "test_globals.h"
#include "test_init.h"
#include "proxysql.h"
#include "proxysql_config.h"
#include "sqlite3db.h"
#include "ProxySQL_Admin_Tables_Definitions.h"
#include <string>
#include <cstring>
// ============================================================
// Helper: create in-memory SQLite3DB
// ============================================================
static SQLite3DB* create_test_db() {
SQLite3DB* db = new SQLite3DB();
db->open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
return db;
}
// ============================================================
// Write_MySQL_Users_to_configfile()
// ============================================================
static void test_write_mysql_users_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_USERS);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Users_to_configfile(data);
ok(rc == 0, "Write_MySQL_Users: returns 0 on empty table");
ok(data.find("mysql_users:") != std::string::npos,
"Write_MySQL_Users: section header present for empty table");
ok(data.find("username") == std::string::npos,
"Write_MySQL_Users: no user fields in empty table");
delete db;
}
static void test_write_mysql_users_single() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_USERS);
db->execute("INSERT INTO mysql_users (username, password, active, use_ssl, "
"default_hostgroup, default_schema, schema_locked, transaction_persistent, "
"fast_forward, backend, frontend, max_connections, attributes, comment) "
"VALUES ('testuser', 'testpass', 1, 0, 1, 'mydb', 0, 1, 0, 1, 1, 1000, '', 'test comment')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Users_to_configfile(data);
ok(rc == 0, "Write_MySQL_Users single: returns 0");
ok(data.find("mysql_users:") != std::string::npos,
"Write_MySQL_Users single: section header present");
ok(data.find("\"testuser\"") != std::string::npos,
"Write_MySQL_Users single: username value quoted");
ok(data.find("\"testpass\"") != std::string::npos,
"Write_MySQL_Users single: password value quoted");
ok(data.find("default_hostgroup=1") != std::string::npos,
"Write_MySQL_Users single: default_hostgroup unquoted integer");
ok(data.find("\"mydb\"") != std::string::npos,
"Write_MySQL_Users single: default_schema quoted");
ok(data.find("\"test comment\"") != std::string::npos,
"Write_MySQL_Users single: comment quoted");
delete db;
}
static void test_write_mysql_users_multiple() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_USERS);
db->execute("INSERT INTO mysql_users (username, password, backend, frontend) "
"VALUES ('user1', 'pass1', 1, 1)");
db->execute("INSERT INTO mysql_users (username, password, backend, frontend) "
"VALUES ('user2', 'pass2', 1, 0)");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Users_to_configfile(data);
ok(rc == 0, "Write_MySQL_Users multiple: returns 0");
ok(data.find("\"user1\"") != std::string::npos,
"Write_MySQL_Users multiple: first user present");
ok(data.find("\"user2\"") != std::string::npos,
"Write_MySQL_Users multiple: second user present");
// Multiple entries are comma-separated
ok(data.find(",\n") != std::string::npos,
"Write_MySQL_Users multiple: entries are comma-separated");
delete db;
}
// ============================================================
// Write_Scheduler_to_configfile()
// ============================================================
static void test_write_scheduler_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_SCHEDULER);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Scheduler_to_configfile(data);
ok(rc == 0, "Write_Scheduler empty: returns 0");
ok(data.find("scheduler:") != std::string::npos,
"Write_Scheduler empty: section header present");
delete db;
}
static void test_write_scheduler_single() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_SCHEDULER);
db->execute("INSERT INTO scheduler (id, active, interval_ms, filename, arg1, arg2, arg3, arg4, arg5, comment) "
"VALUES (1, 1, 5000, '/usr/bin/check.sh', 'arg_one', NULL, NULL, NULL, NULL, 'health check')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Scheduler_to_configfile(data);
ok(rc == 0, "Write_Scheduler single: returns 0");
ok(data.find("scheduler:") != std::string::npos,
"Write_Scheduler single: section header present");
ok(data.find("id=1") != std::string::npos,
"Write_Scheduler single: id unquoted");
ok(data.find("interval_ms=5000") != std::string::npos,
"Write_Scheduler single: interval_ms present");
ok(data.find("\"/usr/bin/check.sh\"") != std::string::npos,
"Write_Scheduler single: filename quoted");
ok(data.find("\"arg_one\"") != std::string::npos,
"Write_Scheduler single: arg1 quoted");
ok(data.find("\"health check\"") != std::string::npos,
"Write_Scheduler single: comment quoted");
delete db;
}
static void test_write_scheduler_null_args() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_SCHEDULER);
db->execute("INSERT INTO scheduler (id, active, interval_ms, filename, comment) "
"VALUES (2, 1, 1000, '/usr/bin/run.sh', '')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Scheduler_to_configfile(data);
ok(rc == 0, "Write_Scheduler null args: returns 0");
// NULL args should not produce output (addField skips NULL)
ok(data.find("arg1") == std::string::npos,
"Write_Scheduler null args: arg1 absent when NULL");
ok(data.find("arg2") == std::string::npos,
"Write_Scheduler null args: arg2 absent when NULL");
delete db;
}
// ============================================================
// Write_Restapi_to_configfile()
// ============================================================
static void test_write_restapi_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_RESTAPI_ROUTES);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Restapi_to_configfile(data);
ok(rc == 0, "Write_Restapi empty: returns 0");
ok(data.find("restapi_routes:") != std::string::npos,
"Write_Restapi empty: section header present");
delete db;
}
static void test_write_restapi_single() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_RESTAPI_ROUTES);
db->execute("INSERT INTO restapi_routes (id, active, timeout_ms, method, uri, script, comment) "
"VALUES (1, 1, 5000, 'GET', '/v1/health', '/usr/bin/health.sh', 'health endpoint')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Restapi_to_configfile(data);
ok(rc == 0, "Write_Restapi single: returns 0");
ok(data.find("id=1") != std::string::npos,
"Write_Restapi single: id unquoted");
ok(data.find("timeout_ms=5000") != std::string::npos,
"Write_Restapi single: timeout_ms unquoted");
// method is field[3] which uses empty dq="" so unquoted
ok(data.find("method=") != std::string::npos,
"Write_Restapi single: method present");
ok(data.find("\"/v1/health\"") != std::string::npos,
"Write_Restapi single: uri quoted");
ok(data.find("\"/usr/bin/health.sh\"") != std::string::npos,
"Write_Restapi single: script quoted");
ok(data.find("\"health endpoint\"") != std::string::npos,
"Write_Restapi single: comment quoted");
delete db;
}
// ============================================================
// Write_MySQL_Query_Rules_to_configfile()
// ============================================================
static void test_write_query_rules_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_QUERY_RULES);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Query_Rules_to_configfile(data);
ok(rc == 0, "Write_Query_Rules empty: returns 0");
ok(data.find("mysql_query_rules:") != std::string::npos,
"Write_Query_Rules empty: section header present");
delete db;
}
static void test_write_query_rules_single() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_QUERY_RULES);
db->execute("INSERT INTO mysql_query_rules "
"(rule_id, active, username, schemaname, flagIN, match_digest, "
"destination_hostgroup, apply, comment) "
"VALUES (1, 1, 'admin', 'testdb', 0, '^SELECT', 10, 1, 'route selects')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Query_Rules_to_configfile(data);
ok(rc == 0, "Write_Query_Rules single: returns 0");
ok(data.find("mysql_query_rules:") != std::string::npos,
"Write_Query_Rules single: section header present");
ok(data.find("rule_id=1") != std::string::npos,
"Write_Query_Rules single: rule_id unquoted");
ok(data.find("active=1") != std::string::npos,
"Write_Query_Rules single: active unquoted");
ok(data.find("\"admin\"") != std::string::npos,
"Write_Query_Rules single: username quoted");
ok(data.find("\"^SELECT\"") != std::string::npos,
"Write_Query_Rules single: match_digest quoted");
ok(data.find("destination_hostgroup=10") != std::string::npos,
"Write_Query_Rules single: destination_hostgroup unquoted");
ok(data.find("apply=1") != std::string::npos,
"Write_Query_Rules single: apply unquoted");
delete db;
}
static void test_write_query_rules_null_fields() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_QUERY_RULES);
// Insert a minimal rule — many optional fields will be NULL
db->execute("INSERT INTO mysql_query_rules (rule_id, active, apply) "
"VALUES (5, 0, 0)");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Query_Rules_to_configfile(data);
ok(rc == 0, "Write_Query_Rules nulls: returns 0");
ok(data.find("rule_id=5") != std::string::npos,
"Write_Query_Rules nulls: rule_id present");
// NULL fields should not appear (addField skips them)
ok(data.find("username") == std::string::npos,
"Write_Query_Rules nulls: NULL username absent");
ok(data.find("match_digest") == std::string::npos,
"Write_Query_Rules nulls: NULL match_digest absent");
ok(data.find("error_msg") == std::string::npos,
"Write_Query_Rules nulls: NULL error_msg absent");
delete db;
}
// ============================================================
// Write_MySQL_Servers_to_configfile()
// ============================================================
static void test_write_mysql_servers_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_SERVERS);
// Also create the sub-tables that Write_MySQL_Servers reads
db->execute("CREATE TABLE mysql_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, check_type VARCHAR, comment VARCHAR)");
db->execute("CREATE TABLE mysql_group_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_galera_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_aws_aurora_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, active INT, aurora_port INT, "
"domain_name VARCHAR, max_lag_ms INT, check_interval_ms INT, check_timeout_ms INT, "
"writer_is_also_reader INT, new_reader_weight INT, add_lag_ms INT, min_lag_ms INT, "
"lag_num_checks INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_hostgroup_attributes "
"(hostgroup_id INT PRIMARY KEY, max_num_online_servers INT, autocommit INT, "
"free_connections_pct INT, init_connect VARCHAR, multiplex INT, connection_warming INT, "
"throttle_connections_per_sec INT, ignore_session_variables VARCHAR, "
"hostgroup_settings VARCHAR, servers_defaults VARCHAR, comment VARCHAR)");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Servers_to_configfile(data);
ok(rc == 0, "Write_MySQL_Servers empty: returns 0");
ok(data.find("mysql_servers:") != std::string::npos,
"Write_MySQL_Servers empty: mysql_servers section present");
ok(data.find("mysql_replication_hostgroups:") != std::string::npos,
"Write_MySQL_Servers empty: replication_hostgroups section present");
ok(data.find("mysql_group_replication_hostgroups:") != std::string::npos,
"Write_MySQL_Servers empty: group_replication_hostgroups section present");
ok(data.find("mysql_galera_hostgroups:") != std::string::npos,
"Write_MySQL_Servers empty: galera_hostgroups section present");
ok(data.find("mysql_aws_aurora_hostgroups:") != std::string::npos,
"Write_MySQL_Servers empty: aws_aurora_hostgroups section present");
ok(data.find("mysql_hostgroup_attributes:") != std::string::npos,
"Write_MySQL_Servers empty: hostgroup_attributes section present");
delete db;
}
static void test_write_mysql_servers_with_data() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_SERVERS);
db->execute("INSERT INTO mysql_servers (hostgroup_id, hostname, port, gtid_port, status, "
"weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment) "
"VALUES (1, '127.0.0.1', 3306, 0, 'ONLINE', 100, 0, 500, 10, 1, 50, 'primary')");
// Create sub-tables (empty) to avoid query errors
db->execute("CREATE TABLE mysql_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, check_type VARCHAR, comment VARCHAR)");
db->execute("CREATE TABLE mysql_group_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_galera_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_aws_aurora_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, active INT, aurora_port INT, "
"domain_name VARCHAR, max_lag_ms INT, check_interval_ms INT, check_timeout_ms INT, "
"writer_is_also_reader INT, new_reader_weight INT, add_lag_ms INT, min_lag_ms INT, "
"lag_num_checks INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_hostgroup_attributes "
"(hostgroup_id INT PRIMARY KEY, max_num_online_servers INT, autocommit INT, "
"free_connections_pct INT, init_connect VARCHAR, multiplex INT, connection_warming INT, "
"throttle_connections_per_sec INT, ignore_session_variables VARCHAR, "
"hostgroup_settings VARCHAR, servers_defaults VARCHAR, comment VARCHAR)");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Servers_to_configfile(data);
ok(rc == 0, "Write_MySQL_Servers data: returns 0");
ok(data.find("hostgroup_id=1") != std::string::npos,
"Write_MySQL_Servers data: hostgroup_id unquoted");
ok(data.find("\"127.0.0.1\"") != std::string::npos,
"Write_MySQL_Servers data: hostname quoted");
ok(data.find("port=3306") != std::string::npos,
"Write_MySQL_Servers data: port unquoted");
ok(data.find("weight=100") != std::string::npos,
"Write_MySQL_Servers data: weight unquoted");
ok(data.find("\"ONLINE\"") != std::string::npos,
"Write_MySQL_Servers data: status quoted");
ok(data.find("use_ssl=1") != std::string::npos,
"Write_MySQL_Servers data: use_ssl unquoted");
ok(data.find("\"primary\"") != std::string::npos,
"Write_MySQL_Servers data: comment quoted");
delete db;
}
static void test_write_mysql_servers_replication_hostgroups() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_SERVERS);
db->execute("CREATE TABLE mysql_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, check_type VARCHAR, comment VARCHAR)");
db->execute("INSERT INTO mysql_replication_hostgroups VALUES (10, 20, 'read_only', 'repl group')");
db->execute("CREATE TABLE mysql_group_replication_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_galera_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, backup_writer_hostgroup INT, reader_hostgroup INT, "
"offline_hostgroup INT, active INT, max_writers INT, writer_is_also_reader INT, "
"max_transactions_behind INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_aws_aurora_hostgroups "
"(writer_hostgroup INT PRIMARY KEY, reader_hostgroup INT, active INT, aurora_port INT, "
"domain_name VARCHAR, max_lag_ms INT, check_interval_ms INT, check_timeout_ms INT, "
"writer_is_also_reader INT, new_reader_weight INT, add_lag_ms INT, min_lag_ms INT, "
"lag_num_checks INT, comment VARCHAR)");
db->execute("CREATE TABLE mysql_hostgroup_attributes "
"(hostgroup_id INT PRIMARY KEY, max_num_online_servers INT, autocommit INT, "
"free_connections_pct INT, init_connect VARCHAR, multiplex INT, connection_warming INT, "
"throttle_connections_per_sec INT, ignore_session_variables VARCHAR, "
"hostgroup_settings VARCHAR, servers_defaults VARCHAR, comment VARCHAR)");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Servers_to_configfile(data);
ok(rc == 0, "Write_Servers repl hg: returns 0");
ok(data.find("mysql_replication_hostgroups:") != std::string::npos,
"Write_Servers repl hg: section header present");
ok(data.find("writer_hostgroup=10") != std::string::npos,
"Write_Servers repl hg: writer_hostgroup present");
ok(data.find("reader_hostgroup=20") != std::string::npos,
"Write_Servers repl hg: reader_hostgroup present");
ok(data.find("\"read_only\"") != std::string::npos,
"Write_Servers repl hg: check_type quoted");
ok(data.find("\"repl group\"") != std::string::npos,
"Write_Servers repl hg: comment quoted");
delete db;
}
// ============================================================
// Write_Global_Variables_to_configfile()
// ============================================================
static void test_write_global_variables_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_GLOBAL_VARIABLES);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Global_Variables_to_configfile(data);
ok(rc == 0, "Write_Global_Vars empty: returns 0");
ok(data.empty(), "Write_Global_Vars empty: no output for empty table");
delete db;
}
static void test_write_global_variables_single_prefix() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_GLOBAL_VARIABLES);
db->execute("INSERT INTO global_variables VALUES ('mysql-max_connections', '2048')");
db->execute("INSERT INTO global_variables VALUES ('mysql-default_query_delay', '0')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Global_Variables_to_configfile(data);
ok(rc == 0, "Write_Global_Vars single prefix: returns 0");
ok(data.find("mysql_variables") != std::string::npos,
"Write_Global_Vars single prefix: section header uses underscore");
// The variable name should have the prefix stripped
ok(data.find("max_connections=\"2048\"") != std::string::npos,
"Write_Global_Vars single prefix: variable name has prefix stripped");
ok(data.find("default_query_delay=\"0\"") != std::string::npos,
"Write_Global_Vars single prefix: second variable present");
// Section should start with { and end with }
ok(data.find("{\n") != std::string::npos,
"Write_Global_Vars single prefix: opening brace present");
ok(data.find("}\n") != std::string::npos,
"Write_Global_Vars single prefix: closing brace present");
delete db;
}
static void test_write_global_variables_multiple_prefixes() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_GLOBAL_VARIABLES);
db->execute("INSERT INTO global_variables VALUES ('admin-admin_credentials', 'admin:admin')");
db->execute("INSERT INTO global_variables VALUES ('mysql-max_connections', '1024')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Global_Variables_to_configfile(data);
ok(rc == 0, "Write_Global_Vars multi prefix: returns 0");
ok(data.find("admin_variables") != std::string::npos,
"Write_Global_Vars multi prefix: admin section present");
ok(data.find("mysql_variables") != std::string::npos,
"Write_Global_Vars multi prefix: mysql section present");
ok(data.find("admin_credentials=\"admin:admin\"") != std::string::npos,
"Write_Global_Vars multi prefix: admin var present with prefix stripped");
ok(data.find("max_connections=\"1024\"") != std::string::npos,
"Write_Global_Vars multi prefix: mysql var present with prefix stripped");
delete db;
}
static void test_write_global_variables_empty_value() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_GLOBAL_VARIABLES);
db->execute("INSERT INTO global_variables VALUES ('mysql-blank_val', '')");
db->execute("INSERT INTO global_variables VALUES ('mysql-nonempty_var', 'value')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Global_Variables_to_configfile(data);
ok(rc == 0, "Write_Global_Vars empty val: returns 0");
// Empty values should be skipped (the code checks strlen)
ok(data.find("blank_val") == std::string::npos,
"Write_Global_Vars empty val: empty value variable skipped");
ok(data.find("nonempty_var=\"value\"") != std::string::npos,
"Write_Global_Vars empty val: non-empty variable present");
delete db;
}
// ============================================================
// Write_ProxySQL_Servers_to_configfile()
// ============================================================
static void test_write_proxysql_servers_empty() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_PROXYSQL_SERVERS);
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_ProxySQL_Servers_to_configfile(data);
ok(rc == 0, "Write_ProxySQL_Servers empty: returns 0");
ok(data.find("proxysql_servers:") != std::string::npos,
"Write_ProxySQL_Servers empty: section header present");
delete db;
}
static void test_write_proxysql_servers_single() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_PROXYSQL_SERVERS);
db->execute("INSERT INTO proxysql_servers VALUES ('proxy1.example.com', 6032, 100, 'node 1')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_ProxySQL_Servers_to_configfile(data);
ok(rc == 0, "Write_ProxySQL_Servers single: returns 0");
ok(data.find("\"proxy1.example.com\"") != std::string::npos,
"Write_ProxySQL_Servers single: hostname quoted");
ok(data.find("port=6032") != std::string::npos,
"Write_ProxySQL_Servers single: port unquoted");
ok(data.find("weight=100") != std::string::npos,
"Write_ProxySQL_Servers single: weight unquoted");
ok(data.find("\"node 1\"") != std::string::npos,
"Write_ProxySQL_Servers single: comment quoted");
delete db;
}
// ============================================================
// Edge cases: special characters and quoting
// ============================================================
static void test_write_mysql_users_special_chars() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_MYSQL_USERS);
// Username with a double-quote character to test escaping
db->execute("INSERT INTO mysql_users (username, password, backend, frontend, comment) "
"VALUES ('test\"user', 'pass', 1, 1, 'comment with \"quotes\"')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Users_to_configfile(data);
ok(rc == 0, "Write_MySQL_Users special chars: returns 0");
// addField escapes " to \"
ok(data.find("\\\"") != std::string::npos,
"Write_MySQL_Users special chars: double quotes escaped in output");
delete db;
}
static void test_write_scheduler_multiple() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_SCHEDULER);
db->execute("INSERT INTO scheduler (id, active, interval_ms, filename, comment) "
"VALUES (1, 1, 1000, '/check1.sh', 'first')");
db->execute("INSERT INTO scheduler (id, active, interval_ms, filename, comment) "
"VALUES (2, 0, 2000, '/check2.sh', 'second')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Scheduler_to_configfile(data);
ok(rc == 0, "Write_Scheduler multiple: returns 0");
ok(data.find("\"/check1.sh\"") != std::string::npos,
"Write_Scheduler multiple: first entry present");
ok(data.find("\"/check2.sh\"") != std::string::npos,
"Write_Scheduler multiple: second entry present");
ok(data.find(",\n") != std::string::npos,
"Write_Scheduler multiple: entries comma-separated");
// Verify proper structure: opening and closing
ok(data.find("(\n") != std::string::npos,
"Write_Scheduler multiple: list opening paren");
ok(data.find("\n)\n") != std::string::npos,
"Write_Scheduler multiple: list closing paren");
delete db;
}
static void test_write_restapi_multiple() {
SQLite3DB* db = create_test_db();
db->execute(ADMIN_SQLITE_TABLE_RESTAPI_ROUTES);
db->execute("INSERT INTO restapi_routes (id, active, timeout_ms, method, uri, script, comment) "
"VALUES (1, 1, 1000, 'GET', '/v1/a', '/a.sh', 'route a')");
db->execute("INSERT INTO restapi_routes (id, active, timeout_ms, method, uri, script, comment) "
"VALUES (2, 1, 2000, 'POST', '/v1/b', '/b.sh', 'route b')");
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Restapi_to_configfile(data);
ok(rc == 0, "Write_Restapi multiple: returns 0");
ok(data.find("\"/v1/a\"") != std::string::npos,
"Write_Restapi multiple: first route URI present");
ok(data.find("\"/v1/b\"") != std::string::npos,
"Write_Restapi multiple: second route URI present");
ok(data.find(",\n") != std::string::npos,
"Write_Restapi multiple: entries comma-separated");
delete db;
}
// ============================================================
// Nonexistent table error handling
// ============================================================
static void test_write_mysql_users_no_table() {
SQLite3DB* db = create_test_db();
// Do NOT create the mysql_users table
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Users_to_configfile(data);
ok(rc == -1, "Write_MySQL_Users no table: returns -1 on missing table");
delete db;
}
static void test_write_scheduler_no_table() {
SQLite3DB* db = create_test_db();
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Scheduler_to_configfile(data);
ok(rc == -1, "Write_Scheduler no table: returns -1 on missing table");
delete db;
}
static void test_write_restapi_no_table() {
SQLite3DB* db = create_test_db();
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Restapi_to_configfile(data);
ok(rc == -1, "Write_Restapi no table: returns -1 on missing table");
delete db;
}
static void test_write_query_rules_no_table() {
SQLite3DB* db = create_test_db();
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_MySQL_Query_Rules_to_configfile(data);
ok(rc == -1, "Write_Query_Rules no table: returns -1 on missing table");
delete db;
}
static void test_write_global_vars_no_table() {
SQLite3DB* db = create_test_db();
ProxySQL_Config cfg(db);
std::string data;
int rc = cfg.Write_Global_Variables_to_configfile(data);
ok(rc == -1, "Write_Global_Vars no table: returns -1 on missing table");
delete db;
}
// ============================================================
// Main
// ============================================================
int main() {
plan(111);
test_init_minimal();
// Write_MySQL_Users_to_configfile
test_write_mysql_users_empty(); // 3
test_write_mysql_users_single(); // 7
test_write_mysql_users_multiple(); // 4
test_write_mysql_users_special_chars(); // 2
// Write_Scheduler_to_configfile
test_write_scheduler_empty(); // 2
test_write_scheduler_single(); // 7
test_write_scheduler_null_args(); // 3
test_write_scheduler_multiple(); // 6
// Write_Restapi_to_configfile
test_write_restapi_empty(); // 2
test_write_restapi_single(); // 7
test_write_restapi_multiple(); // 4
// Write_MySQL_Query_Rules_to_configfile
test_write_query_rules_empty(); // 2
test_write_query_rules_single(); // 8
test_write_query_rules_null_fields(); // 5
// Write_MySQL_Servers_to_configfile
test_write_mysql_servers_empty(); // 7
test_write_mysql_servers_with_data(); // 8
test_write_mysql_servers_replication_hostgroups(); // 6
// Write_Global_Variables_to_configfile
test_write_global_variables_empty(); // 2
test_write_global_variables_single_prefix(); // 6
test_write_global_variables_multiple_prefixes();// 5
test_write_global_variables_empty_value(); // 3
// Write_ProxySQL_Servers_to_configfile
test_write_proxysql_servers_empty(); // 2
test_write_proxysql_servers_single(); // 5
// Error handling: missing tables
test_write_mysql_users_no_table(); // 1
test_write_scheduler_no_table(); // 1
test_write_restapi_no_table(); // 1
test_write_query_rules_no_table(); // 1
test_write_global_vars_no_table(); // 1
test_cleanup_minimal();
return exit_status();
}