summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-10-09 17:28:53 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2017-10-10 23:21:06 +0200
commitf3e8f36f4066bb3f02db6755686679c311e104ec (patch)
tree151d449fb881ce945bbf8b9bb25ec6e1d2db8058
parent715e3f6397db19caa177d523ea7c2731ed3901e3 (diff)
add initial db_test: creating and deleting subscribers
-rw-r--r--configure.ac1
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/db/Makefile.am53
-rw-r--r--tests/db/db_test.c347
-rw-r--r--tests/db/db_test.err251
-rw-r--r--tests/db/db_test.ok2
-rw-r--r--tests/testsuite.at8
7 files changed, 663 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 167d7f3..1db32d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,4 +70,5 @@ AC_OUTPUT(
tests/auc/Makefile
tests/auc/gen_ts_55_205_test_sets/Makefile
tests/gsup_server/Makefile
+ tests/db/Makefile
)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d979fb6..0b625f5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,6 +1,7 @@
SUBDIRS = \
auc \
gsup_server \
+ db \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/db/Makefile.am b/tests/db/Makefile.am
new file mode 100644
index 0000000..a1f35a7
--- /dev/null
+++ b/tests/db/Makefile.am
@@ -0,0 +1,53 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/src \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(SQLITE3_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(NULL)
+
+EXTRA_DIST = \
+ db_test.ok \
+ db_test.err \
+ $(NULL)
+
+check_PROGRAMS = db_test
+
+db_test_SOURCES = \
+ db_test.c \
+ $(NULL)
+
+db_test_LDADD = \
+ $(top_srcdir)/src/db.c \
+ $(top_srcdir)/src/db_hlr.c \
+ $(top_srcdir)/src/db_auc.c \
+ $(top_srcdir)/src/logging.c \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(SQLITE3_LIBS) \
+ $(NULL)
+
+.PHONY: db_test.db update_exp manual manual-nonverbose manual-gdb
+db_test.db:
+ rm -f db_test.db
+ sqlite3 $(builddir)/db_test.db < $(top_srcdir)/sql/hlr.sql
+
+update_exp: db_test.db
+ cd $(builddir); ./db_test >"$(srcdir)/db_test.ok" 2>"$(srcdir)/db_test.err"
+
+manual: db_test.db
+ cd $(builddir); ./db_test -v
+
+manual-nonverbose: db_test.db
+ cd $(builddir); ./db_test
+
+manual-gdb: db_test.db
+ cd $(builddir); gdb -ex run --args ./db_test -v
diff --git a/tests/db/db_test.c b/tests/db/db_test.c
new file mode 100644
index 0000000..e8cfe75
--- /dev/null
+++ b/tests/db/db_test.c
@@ -0,0 +1,347 @@
+/* (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+#include "db.h"
+#include "logging.h"
+
+#define comment_start() fprintf(stderr, "\n===== %s\n", __func__);
+#define comment(fmt, args...) fprintf(stderr, "\n--- " fmt "\n\n", ## args);
+#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__);
+
+/* Perform a function call and verbosely assert that its return value is as expected.
+ * The return code is then available in g_rc. */
+#define ASSERT_RC(call, expect_rc) \
+ do { \
+ fprintf(stderr, #call " --> " #expect_rc "\n"); \
+ g_rc = call; \
+ if (g_rc != (expect_rc)) \
+ fprintf(stderr, " MISMATCH: got rc = %d, expected: " \
+ #expect_rc " = %d\n", g_rc, expect_rc); \
+ OSMO_ASSERT(g_rc == (expect_rc)); \
+ fprintf(stderr, "\n"); \
+ } while (0)
+
+/* Do db_subscr_get_by_xxxx and verbosely assert that its return value is as expected.
+ * Print the subscriber struct to stderr to be validated by db_test.err.
+ * The result is then available in g_subscr. */
+#define ASSERT_SEL(by, val, expect_rc) \
+ do { \
+ int rc; \
+ g_subscr = (struct hlr_subscriber){}; \
+ fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \
+ #expect_rc "\n"); \
+ rc = db_subscr_get_by_##by(dbc, val, &g_subscr); \
+ if (rc != (expect_rc)) \
+ fprintf(stderr, " MISMATCH: got rc = %d, expected: " \
+ #expect_rc " = %d\n", rc, expect_rc); \
+ OSMO_ASSERT(rc == (expect_rc)); \
+ if (!rc) \
+ dump_subscr(&g_subscr); \
+ fprintf(stderr, "\n"); \
+ } while (0)
+
+static struct db_context *dbc = NULL;
+static void *ctx = NULL;
+static struct hlr_subscriber g_subscr;
+static int g_rc;
+
+#define Pfv(name, fmt, val) \
+ fprintf(stderr, " ." #name " = " fmt ",\n", val)
+#define Pfo(name, fmt, obj) \
+ Pfv(name, fmt, obj->name)
+
+/* Print a subscriber struct to stderr to be validated by db_test.err. */
+void dump_subscr(struct hlr_subscriber *subscr)
+{
+#define Ps(name) \
+ if (*subscr->name) \
+ Pfo(name, "'%s'", subscr)
+#define Pd(name) \
+ Pfv(name, "%"PRId64, (int64_t)subscr->name)
+#define Pd_nonzero(name) \
+ if (subscr->name) \
+ Pd(name)
+#define Pb(if_val, name) \
+ if (subscr->name == (if_val)) \
+ Pfv(name, "%s", subscr->name ? "true" : "false")
+
+ fprintf(stderr, "struct hlr_subscriber {\n");
+ Pd(id);
+ Ps(imsi);
+ Ps(msisdn);
+ Ps(vlr_number);
+ Ps(sgsn_number);
+ Ps(sgsn_address);
+ Ps(sgsn_address);
+ Pd_nonzero(periodic_lu_timer);
+ Pd_nonzero(periodic_rau_tau_timer);
+ Pb(false, nam_cs);
+ Pb(false, nam_ps);
+ if (subscr->lmsi)
+ Pfo(lmsi, "0x%x", subscr);
+ Pb(true, ms_purged_cs);
+ Pb(true, ms_purged_ps);
+ fprintf(stderr, "}\n");
+#undef Ps
+#undef Pd
+#undef Pd_nonzero
+#undef Pb
+}
+
+void dump_aud(const char *label, struct osmo_sub_auth_data *aud)
+{
+ if (aud->type == OSMO_AUTH_TYPE_NONE) {
+ fprintf(stderr, "%s: none\n", label);
+ return;
+ }
+
+ fprintf(stderr, "%s: struct osmo_sub_auth_data {\n", label);
+#define Pf(name, fmt) \
+ Pfo(name, fmt, aud)
+#define Phex(name) \
+ Pfv(name, "'%s'", osmo_hexdump_nospc(aud->name, sizeof(aud->name)))
+
+ Pfv(type, "%s", osmo_sub_auth_type_name(aud->type));
+ Pfv(algo, "%s", osmo_auth_alg_name(aud->algo));
+ switch (aud->type) {
+ case OSMO_AUTH_TYPE_GSM:
+ Phex(u.gsm.ki);
+ break;
+ case OSMO_AUTH_TYPE_UMTS:
+ Phex(u.umts.opc);
+ Pf(u.umts.opc_is_op, "%u");
+ Phex(u.umts.k);
+ Phex(u.umts.amf);
+ if (aud->u.umts.sqn) {
+ Pf(u.umts.sqn, "%"PRIu64);
+ Pf(u.umts.sqn, "0x%"PRIx64);
+ }
+ if (aud->u.umts.ind_bitlen)
+ Pf(u.umts.ind_bitlen, "%u");
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+
+ fprintf(stderr, "}\n");
+
+#undef Pf
+#undef Phex
+}
+
+static const char *imsi0 = "123456789000000";
+static const char *imsi1 = "123456789000001";
+static const char *imsi2 = "123456789000002";
+static const char *short_imsi = "123456";
+static const char *unknown_imsi = "999999999";
+
+static void test_subscr_create_update_sel_delete()
+{
+ int64_t id0, id1, id2, id_short;
+ comment_start();
+
+ comment("Create with valid / invalid IMSI");
+
+ ASSERT_RC(db_subscr_create(dbc, imsi0), 0);
+ ASSERT_SEL(imsi, imsi0, 0);
+ id0 = g_subscr.id;
+ ASSERT_RC(db_subscr_create(dbc, imsi1), 0);
+ ASSERT_SEL(imsi, imsi1, 0);
+ id1 = g_subscr.id;
+ ASSERT_RC(db_subscr_create(dbc, imsi2), 0);
+ ASSERT_SEL(imsi, imsi2, 0);
+ id2 = g_subscr.id;
+ ASSERT_RC(db_subscr_create(dbc, imsi0), -EIO);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO);
+ ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO);
+ ASSERT_SEL(imsi, imsi1, 0);
+ ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO);
+ ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO);
+ ASSERT_SEL(imsi, imsi2, 0);
+
+ ASSERT_RC(db_subscr_create(dbc, "123456789 000003"), -EINVAL);
+ ASSERT_SEL(imsi, "123456789000003", -ENOEXEC);
+
+ ASSERT_RC(db_subscr_create(dbc, "123456789000002123456"), -EINVAL);
+ ASSERT_SEL(imsi, "123456789000002123456", -ENOEXEC);
+
+ ASSERT_RC(db_subscr_create(dbc, "foobar123"), -EINVAL);
+ ASSERT_SEL(imsi, "foobar123", -ENOEXEC);
+
+ ASSERT_RC(db_subscr_create(dbc, "123"), -EINVAL);
+ ASSERT_SEL(imsi, "123", -ENOEXEC);
+
+ ASSERT_RC(db_subscr_create(dbc, short_imsi), 0);
+ ASSERT_SEL(imsi, short_imsi, 0);
+ id_short = g_subscr.id;
+
+
+ comment("Set valid / invalid MSISDN");
+
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321"), 0);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "54321012345678912345678"), -EINVAL);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "543 21"), -EINVAL);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "foobar123"), -EINVAL);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "5"), 0);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "543210123456789"), 0);
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
+ "5432101234567891"), -EINVAL);
+ ASSERT_SEL(imsi, imsi0, 0);
+
+ comment("Set MSISDN on non-existent / invalid IMSI");
+
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99"), -ENOENT);
+
+ ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99"), -ENOENT);
+
+ comment("Delete non-existent / invalid IDs");
+
+ ASSERT_RC(db_subscr_delete_by_id(dbc, 999), -ENOENT);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, -10), -ENOENT);
+
+ comment("Delete subscribers");
+
+ ASSERT_SEL(imsi, imsi0, 0);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, id0), 0);
+ ASSERT_SEL(imsi, imsi0, -ENOEXEC);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, id0), -ENOENT);
+
+ ASSERT_SEL(imsi, imsi1, 0);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, id1), 0);
+ ASSERT_SEL(imsi, imsi1, -ENOEXEC);
+
+ ASSERT_SEL(imsi, imsi2, 0);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, id2), 0);
+ ASSERT_SEL(imsi, imsi2, -ENOEXEC);
+
+ ASSERT_SEL(imsi, short_imsi, 0);
+ ASSERT_RC(db_subscr_delete_by_id(dbc, id_short), 0);
+ ASSERT_SEL(imsi, short_imsi, -ENOEXEC);
+
+ comment_end();
+}
+
+static struct {
+ bool verbose;
+} cmdline_opts = {
+ .verbose = false,
+};
+
+static void print_help(const char *program)
+{
+ printf("Usage:\n"
+ " %s [-v] [N [N...]]\n"
+ "Options:\n"
+ " -h --help show this text.\n"
+ " -v --verbose print source file and line numbers\n",
+ program
+ );
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"verbose", 1, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hv",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help(argv[0]);
+ exit(0);
+ case 'v':
+ cmdline_opts.verbose = true;
+ break;
+ default:
+ /* catch unknown options *as well as* missing arguments. */
+ fprintf(stderr, "Error in command line options. Exiting.\n");
+ exit(-1);
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr, "too many args\n");
+ exit(-1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ printf("db_test.c\n");
+
+ ctx = talloc_named_const(NULL, 1, "db_test");
+
+ handle_options(argc, argv);
+
+ osmo_init_logging(&hlr_log_info);
+ log_set_print_filename(osmo_stderr_target, cmdline_opts.verbose);
+ log_set_print_timestamp(osmo_stderr_target, 0);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 1);
+
+ /* omit the SQLite version and compilation flags from test output */
+ log_set_log_level(osmo_stderr_target, LOGL_ERROR);
+ dbc = db_open(ctx, "db_test.db");
+ log_set_log_level(osmo_stderr_target, 0);
+ OSMO_ASSERT(dbc);
+
+ test_subscr_create_update_sel_delete();
+
+ printf("Done\n");
+ return 0;
+}
+
+/* stubs */
+int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
+ struct osmo_sub_auth_data *aud2g,
+ struct osmo_sub_auth_data *aud3g,
+ const uint8_t *rand_auts, const uint8_t *auts)
+{ OSMO_ASSERT(false); return -1; }
diff --git a/tests/db/db_test.err b/tests/db/db_test.err
new file mode 100644
index 0000000..ac0e2f1
--- /dev/null
+++ b/tests/db/db_test.err
@@ -0,0 +1,251 @@
+
+===== test_subscr_create_update_sel_delete
+
+--- Create with valid / invalid IMSI
+
+db_subscr_create(dbc, imsi0) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+}
+
+db_subscr_create(dbc, imsi1) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 2,
+ .imsi = '123456789000001',
+}
+
+db_subscr_create(dbc, imsi2) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 3,
+ .imsi = '123456789000002',
+}
+
+db_subscr_create(dbc, imsi0) --> -EIO
+DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
+DDB Error in sqlite3_reset: 2067
+DAUC IMSI='123456789000000': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+}
+
+db_subscr_create(dbc, imsi1) --> -EIO
+DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
+DDB Error in sqlite3_reset: 2067
+DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
+
+db_subscr_create(dbc, imsi1) --> -EIO
+DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
+DDB Error in sqlite3_reset: 2067
+DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
+
+db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 2,
+ .imsi = '123456789000001',
+}
+
+db_subscr_create(dbc, imsi2) --> -EIO
+DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
+DDB Error in sqlite3_reset: 2067
+DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
+
+db_subscr_create(dbc, imsi2) --> -EIO
+DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi
+DDB Error in sqlite3_reset: 2067
+DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi
+
+db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 3,
+ .imsi = '123456789000002',
+}
+
+db_subscr_create(dbc, "123456789 000003") --> -EINVAL
+DAUC Cannot create subscriber: invalid IMSI: '123456789 000003'
+
+db_subscr_get_by_imsi(dbc, "123456789000003", &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456789000003': Error executing SQL: 101
+
+db_subscr_create(dbc, "123456789000002123456") --> -EINVAL
+DAUC Cannot create subscriber: invalid IMSI: '123456789000002123456'
+
+db_subscr_get_by_imsi(dbc, "123456789000002123456", &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456789000002123456': Error executing SQL: 101
+
+db_subscr_create(dbc, "foobar123") --> -EINVAL
+DAUC Cannot create subscriber: invalid IMSI: 'foobar123'
+
+db_subscr_get_by_imsi(dbc, "foobar123", &g_subscr) --> -ENOEXEC
+DAUC IMSI='foobar123': Error executing SQL: 101
+
+db_subscr_create(dbc, "123") --> -EINVAL
+DAUC Cannot create subscriber: invalid IMSI: '123'
+
+db_subscr_get_by_imsi(dbc, "123", &g_subscr) --> -ENOEXEC
+DAUC IMSI='123': Error executing SQL: 101
+
+db_subscr_create(dbc, short_imsi) --> 0
+
+db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 4,
+ .imsi = '123456',
+}
+
+
+--- Set valid / invalid MSISDN
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321") --> 0
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '54321',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321012345678912345678") --> -EINVAL
+DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '54321012345678912345678'
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '54321',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543 21") --> -EINVAL
+DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '543 21'
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '54321',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "foobar123") --> -EINVAL
+DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: 'foobar123'
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '54321',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "5") --> 0
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '5',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543210123456789") --> 0
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '543210123456789',
+}
+
+db_subscr_update_msisdn_by_imsi(dbc, imsi0, "5432101234567891") --> -EINVAL
+DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '5432101234567891'
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '543210123456789',
+}
+
+
+--- Set MSISDN on non-existent / invalid IMSI
+
+db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99") --> -ENOENT
+DAUC Cannot update MSISDN: no such subscriber: IMSI='999999999'
+
+db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99") --> -ENOENT
+DAUC Cannot update MSISDN: no such subscriber: IMSI='foobar'
+
+
+--- Delete non-existent / invalid IDs
+
+db_subscr_delete_by_id(dbc, 999) --> -ENOENT
+DAUC Cannot delete: no such subscriber: ID=999
+
+db_subscr_delete_by_id(dbc, -10) --> -ENOENT
+DAUC Cannot delete: no such subscriber: ID=-10
+
+
+--- Delete subscribers
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 1,
+ .imsi = '123456789000000',
+ .msisdn = '543210123456789',
+}
+
+db_subscr_delete_by_id(dbc, id0) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456789000000': Error executing SQL: 101
+
+db_subscr_delete_by_id(dbc, id0) --> -ENOENT
+DAUC Cannot delete: no such subscriber: ID=1
+
+db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 2,
+ .imsi = '123456789000001',
+}
+
+db_subscr_delete_by_id(dbc, id1) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456789000001': Error executing SQL: 101
+
+db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 3,
+ .imsi = '123456789000002',
+}
+
+db_subscr_delete_by_id(dbc, id2) --> 0
+
+db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456789000002': Error executing SQL: 101
+
+db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0
+struct hlr_subscriber {
+ .id = 4,
+ .imsi = '123456',
+}
+
+db_subscr_delete_by_id(dbc, id_short) --> 0
+
+db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> -ENOEXEC
+DAUC IMSI='123456': Error executing SQL: 101
+
+===== test_subscr_create_update_sel_delete: SUCCESS
+
diff --git a/tests/db/db_test.ok b/tests/db/db_test.ok
new file mode 100644
index 0000000..26cefd1
--- /dev/null
+++ b/tests/db/db_test.ok
@@ -0,0 +1,2 @@
+db_test.c
+Done
diff --git a/tests/testsuite.at b/tests/testsuite.at
index a969082..74179e7 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -21,3 +21,11 @@ cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout
cat $abs_srcdir/gsup_server/gsup_server_test.err > experr
AT_CHECK([$abs_top_builddir/tests/gsup_server/gsup_server_test], [], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([db])
+AT_KEYWORDS([db])
+cat $abs_srcdir/db/db_test.ok > expout
+cat $abs_srcdir/db/db_test.err > experr
+sqlite3 db_test.db < $abs_top_srcdir/sql/hlr.sql
+AT_CHECK([$abs_top_builddir/tests/db/db_test], [], [expout], [experr])
+AT_CLEANUP