diff options
Diffstat (limited to 'src/lib-dict-backend/test-dict-sql.c')
-rw-r--r-- | src/lib-dict-backend/test-dict-sql.c | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/src/lib-dict-backend/test-dict-sql.c b/src/lib-dict-backend/test-dict-sql.c new file mode 100644 index 0000000..4de45eb --- /dev/null +++ b/src/lib-dict-backend/test-dict-sql.c @@ -0,0 +1,314 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-lib.h" +#include "sql-api.h" +#include "dict.h" +#include "dict-private.h" +#include "dict-sql.h" +#include "dict-sql-private.h" +#include "driver-test.h" + +struct dict_op_settings dict_op_settings = { + .username = "testuser", +}; + +static void test_setup(struct dict **dict_r) +{ + const char *error = NULL; + struct dict_settings set = { + .base_dir = "." + }; + struct dict *dict = NULL; + + if (dict_init("mysql:" DICT_SRC_DIR "/dict.conf", &set, &dict, &error) < 0) + i_fatal("cannot initialize dict: %s", error); + + *dict_r = dict; +} + +static void test_teardown(struct dict **_dict) +{ + struct dict *dict = *_dict; + *_dict = NULL; + if (dict != NULL) { + dict_deinit(&dict); + } +} + +static void test_set_expected(struct dict *_dict, + const struct test_driver_result *result) +{ + struct sql_dict *dict = + (struct sql_dict *)_dict; + + sql_driver_test_add_expected_result(dict->db, result); +} + +static void test_lookup_one(void) +{ + const char *value = NULL, *error = NULL; + struct test_driver_result_set rset = { + .rows = 1, + .cols = 1, + .col_names = (const char *[]){"value", NULL}, + .row_data = (const char **[]){(const char*[]){"one", NULL}}, + }; + struct test_driver_result res = { + .nqueries = 1, + .queries = (const char *[]){"SELECT value FROM table WHERE a = 'hello' AND b = 'world'", NULL}, + .result = &rset, + }; + const struct dict_op_settings set = { + .username = "testuser", + }; + struct dict *dict; + pool_t pool = pool_datastack_create(); + + test_begin("dict lookup one"); + test_setup(&dict); + + test_set_expected(dict, &res); + + test_assert(dict_lookup(dict, &set, pool, "shared/dictmap/hello/world", &value, &error) == 1); + test_assert_strcmp(value, "one"); + if (error != NULL) + i_error("dict_lookup failed: %s", error); + test_teardown(&dict); + test_end(); +} + +static void test_atomic_inc(void) +{ + const char *error; + struct test_driver_result res = { + .nqueries = 3, + .queries = (const char *[]){ + "UPDATE counters SET value=value+128 WHERE class = 'global' AND name = 'counter'", + "UPDATE quota SET bytes=bytes+128,count=count+1 WHERE username = 'testuser'", + "UPDATE quota SET bytes=bytes+128,count=count+1,folders=folders+123 WHERE username = 'testuser'", + NULL}, + .result = NULL, + }; + struct dict_op_settings set = { + .username = "testuser", + }; + struct dict *dict; + + test_begin("dict atomic inc"); + test_setup(&dict); + + test_set_expected(dict, &res); + + /* 1 field */ + struct dict_transaction_context *ctx = dict_transaction_begin(dict, &set); + dict_atomic_inc(ctx, "shared/counters/global/counter", 128); + test_assert(dict_transaction_commit(&ctx, &error) == 0); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + error = NULL; + + /* 2 fields */ + ctx = dict_transaction_begin(dict, &set); + dict_atomic_inc(ctx, "priv/quota/bytes", 128); + dict_atomic_inc(ctx, "priv/quota/count", 1); + test_assert(dict_transaction_commit(&ctx, &error) == 0); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + error = NULL; + + /* 3 fields */ + ctx = dict_transaction_begin(dict, &set); + dict_atomic_inc(ctx, "priv/quota/bytes", 128); + dict_atomic_inc(ctx, "priv/quota/count", 1); + dict_atomic_inc(ctx, "priv/quota/folders", 123); + test_assert(dict_transaction_commit(&ctx, &error) == 0); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + test_teardown(&dict); + test_end(); +} + +static void test_set(void) +{ + const char *error; + struct test_driver_result res = { + .affected_rows = 1, + .nqueries = 3, + .queries = (const char *[]){ + "INSERT INTO counters (value,class,name) VALUES (128,'global','counter') ON DUPLICATE KEY UPDATE value=128", + "INSERT INTO quota (bytes,count,username) VALUES (128,1,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1", + "INSERT INTO quota (bytes,count,folders,username) VALUES (128,1,123,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1,folders=123", + NULL}, + .result = NULL, + }; + struct dict *dict; + + test_begin("dict set"); + test_setup(&dict); + + test_set_expected(dict, &res); + + /* 1 field */ + struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings); + dict_set(ctx, "shared/counters/global/counter", "128"); + test_assert(dict_transaction_commit(&ctx, &error) == 1); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + error = NULL; + + /* 2 fields */ + ctx = dict_transaction_begin(dict, &dict_op_settings); + dict_set(ctx, "priv/quota/bytes", "128"); + dict_set(ctx, "priv/quota/count", "1"); + test_assert(dict_transaction_commit(&ctx, &error) == 1); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + error = NULL; + + /* 3 fields */ + ctx = dict_transaction_begin(dict, &dict_op_settings); + dict_set(ctx, "priv/quota/bytes", "128"); + dict_set(ctx, "priv/quota/count", "1"); + dict_set(ctx, "priv/quota/folders", "123"); + test_assert(dict_transaction_commit(&ctx, &error) == 1); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + test_teardown(&dict); + test_end(); +} + +static void test_unset(void) +{ + const char *error; + struct test_driver_result res = { + .affected_rows = 1, + .nqueries = 3, + .queries = (const char *[]){ + "DELETE FROM counters WHERE class = 'global' AND name = 'counter'", + "DELETE FROM quota WHERE username = 'testuser'", + "DELETE FROM quota WHERE username = 'testuser'", + NULL}, + .result = NULL, + }; + struct dict *dict; + + test_begin("dict unset"); + test_setup(&dict); + + test_set_expected(dict, &res); + + struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings); + dict_unset(ctx, "shared/counters/global/counter"); + test_assert(dict_transaction_commit(&ctx, &error) == 1); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + error = NULL; + ctx = dict_transaction_begin(dict, &dict_op_settings); + dict_unset(ctx, "priv/quota/bytes"); + dict_unset(ctx, "priv/quota/count"); + test_assert(dict_transaction_commit(&ctx, &error) == 1); + if (error != NULL) + i_error("dict_transaction_commit failed: %s", error); + test_teardown(&dict); + test_end(); +} + +static void test_iterate(void) +{ + const char *key = NULL, *value = NULL, *error; + struct test_driver_result_set rset = { + .rows = 5, + .cols = 2, + .col_names = (const char *[]){"value", "name", NULL}, + .row_data = (const char **[]){ + (const char*[]){"one", "counter", NULL}, + (const char*[]){"two", "counter", NULL}, + (const char*[]){"three", "counter", NULL}, + (const char*[]){"four", "counter", NULL}, + (const char*[]){"five", "counter", NULL}, + }, + }; + struct test_driver_result res = { + .nqueries = 1, + .queries = (const char *[]){ + "SELECT value,name FROM counters WHERE class = 'global' AND name = 'counter'", + NULL}, + .result = &rset, + }; + struct dict *dict; + + test_begin("dict iterate"); + test_setup(&dict); + + test_set_expected(dict, &res); + + struct dict_iterate_context *iter = + dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/counter", + DICT_ITERATE_FLAG_EXACT_KEY); + + size_t idx = 0; + while(dict_iterate(iter, &key, &value)) { + i_assert(idx < rset.rows); + test_assert_strcmp_idx(key, "shared/counters/global/counter", idx); + test_assert_strcmp_idx(value, rset.row_data[idx][0], idx); + idx++; + } + + test_assert(idx == rset.rows); + test_assert(dict_iterate_deinit(&iter, &error) == 0); + if (error != NULL) + i_error("dict_iterate_deinit failed: %s", error); + error = NULL; + + res.queries = (const char*[]){ + "SELECT value,name FROM counters WHERE class = 'global' AND name LIKE '%' AND name NOT LIKE '%/%'", + NULL + }; + + res.cur = 0; + res.result->cur = 0; + + test_set_expected(dict, &res); + + iter = dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/", 0); + + idx = 0; + + while(dict_iterate(iter, &key, &value)) { + i_assert(idx < rset.rows); + test_assert_strcmp_idx(key, "shared/counters/global/counter", idx); + test_assert_strcmp_idx(value, rset.row_data[idx][0], idx); + idx++; + } + + test_assert(idx == rset.rows); + test_assert(dict_iterate_deinit(&iter, &error) == 0); + if (error != NULL) + i_error("dict_iterate_deinit failed: %s", error); + test_teardown(&dict); + test_end(); +} + +int main(void) { + sql_drivers_init(); + sql_driver_test_register(); + dict_sql_register(); + + static void (*const test_functions[])(void) = { + test_lookup_one, + test_atomic_inc, + test_set, + test_unset, + test_iterate, + NULL + }; + + int ret = test_run(test_functions); + + dict_sql_unregister(); + sql_driver_test_unregister(); + sql_drivers_deinit(); + + return ret; +} |