summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2013-02-12 17:40:01 +0100
committerJakub Hrozek <jhrozek@redhat.com>2013-03-08 22:19:26 +0100
commit150b76e13b7c4f3ccf1d709bf517ca2af6b2c9a2 (patch)
tree48def0d6cdd381d89b50e1a7c1c4d59a3e55cd06
parent1a5d83798af7bd88c9d20862c8830ebb5b755e2d (diff)
downloadsssd-150b76e13b7c4f3ccf1d709bf517ca2af6b2c9a2.tar.gz
sssd-150b76e13b7c4f3ccf1d709bf517ca2af6b2c9a2.tar.bz2
sssd-150b76e13b7c4f3ccf1d709bf517ca2af6b2c9a2.zip
CMocka based test for the NSS responder
-rw-r--r--Makefile.am43
-rw-r--r--src/tests/cmocka/common_mock.h54
-rw-r--r--src/tests/cmocka/common_mock_resp.c139
-rw-r--r--src/tests/cmocka/common_mock_resp.h62
-rw-r--r--src/tests/cmocka/test_nss_srv.c512
5 files changed, 810 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index c66e2552..2401c912 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -138,9 +138,15 @@ endif
endif
+if HAVE_CMOCKA
+ non_interactive_cmocka_based_tests = \
+ nss-srv-tests
+endif
+
check_PROGRAMS = \
stress-tests \
krb5-child-test \
+ $(non_interactive_cmocka_based_tests) \
$(non_interactive_check_based_tests)
PYTHON_TESTS =
@@ -153,6 +159,7 @@ endif
TESTS = \
$(PYTHON_TESTS) \
+ $(non_interactive_cmocka_based_tests) \
$(non_interactive_check_based_tests)
sssdlib_LTLIBRARIES = \
@@ -1153,6 +1160,42 @@ krb5_child_test_LDADD = \
libsss_util.la \
libsss_test_common.la
+if HAVE_CMOCKA
+TEST_MOCK_OBJ = \
+ src/tests/common.c
+
+TEST_MOCK_RESP_OBJ = \
+ $(TEST_MOCK_OBJ) \
+ src/tests/common_tev.c \
+ src/tests/common_dom.c \
+ src/tests/cmocka/common_mock_resp.c \
+ src/responder/common/responder_packet.c \
+ src/responder/common/responder_cmd.c \
+ src/responder/common/negcache.c \
+ src/responder/common/responder_common.c
+
+nss_srv_tests_DEPENDENCIES = \
+ $(ldblib_LTLIBRARIES)
+nss_srv_tests_SOURCES = \
+ $(TEST_MOCK_RESP_OBJ) \
+ src/tests/cmocka/test_nss_srv.c \
+ src/responder/nss/nsssrv_cmd.c \
+ src/responder/nss/nsssrv_netgroup.c \
+ src/responder/nss/nsssrv_services.c \
+ src/responder/nss/nsssrv_mmap_cache.c
+nss_srv_tests_CFLAGS = \
+ $(AM_CFLAGS)
+nss_srv_tests_LDFLAGS = \
+ -Wl,-wrap,sss_ncache_check_user \
+ -Wl,-wrap,sss_packet_get_body \
+ -Wl,-wrap,sss_packet_get_cmd \
+ -Wl,-wrap,sss_cmd_send_empty \
+ -Wl,-wrap,sss_cmd_done
+nss_srv_tests_LDADD = \
+ $(CMOCKA_LIBS) \
+ libsss_util.la
+endif
+
noinst_PROGRAMS = pam_test_client
if BUILD_SUDO
noinst_PROGRAMS += sss_sudo_cli
diff --git a/src/tests/cmocka/common_mock.h b/src/tests/cmocka/common_mock.h
new file mode 100644
index 00000000..b54cc087
--- /dev/null
+++ b/src/tests/cmocka/common_mock.h
@@ -0,0 +1,54 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ SSSD tests: Common utilities for tests that exercise domains
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __COMMON_MOCK_H_
+#define __COMMON_MOCK_H_
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "tests/common.h"
+
+#define sss_mock_type(type) ((type) mock())
+#define sss_mock_ptr_type(type) ((type) (uintptr_t) mock())
+
+enum sss_test_wrapper_call {
+ WRAP_CALL_WRAPPER,
+ WRAP_CALL_REAL
+};
+
+#endif /* __COMMON_MOCK_H_ */
diff --git a/src/tests/cmocka/common_mock_resp.c b/src/tests/cmocka/common_mock_resp.c
new file mode 100644
index 00000000..d74f6ffa
--- /dev/null
+++ b/src/tests/cmocka/common_mock_resp.c
@@ -0,0 +1,139 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ SSSD tests: Common utilities for tests that exercise domains
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "tests/cmocka/common_mock_resp.h"
+
+/* Mock a responder context */
+struct resp_ctx *
+mock_rctx(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domains,
+ void *pvt_ctx)
+{
+ struct resp_ctx *rctx;
+ errno_t ret;
+
+ rctx = talloc_zero(mem_ctx, struct resp_ctx);
+ if (!rctx) return NULL;
+
+ ret = sss_hash_create(rctx, 30, &rctx->dp_request_table);
+ if (ret != EOK) {
+ talloc_free(rctx);
+ return NULL;
+ }
+
+ rctx->ev = ev;
+ rctx->domains = domains;
+ rctx->pvt_ctx = pvt_ctx;
+ return rctx;
+}
+
+/* Mock a client context */
+struct cli_ctx *
+mock_cctx(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx)
+{
+ struct cli_ctx *cctx;
+
+ cctx = talloc_zero(mem_ctx, struct cli_ctx);
+ if (!cctx) return NULL;
+
+ cctx->creq = talloc_zero(cctx, struct cli_request);
+ if (cctx->creq == NULL) {
+ talloc_free(cctx);
+ return NULL;
+ }
+
+ cctx->rctx = rctx;
+ return cctx;
+}
+
+/* Mock DP requests that finish immediatelly and return
+ * mocked values as per previous set by mock_account_recv
+ */
+struct tevent_req *
+sss_dp_get_account_send(TALLOC_CTX *mem_ctx,
+ struct resp_ctx *rctx,
+ struct sss_domain_info *dom,
+ bool fast_reply,
+ enum sss_dp_acct_type type,
+ const char *opt_name,
+ uint32_t opt_id,
+ const char *extra)
+{
+ return test_req_succeed_send(mem_ctx, rctx->ev);
+}
+
+
+errno_t
+sss_dp_get_account_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ dbus_uint16_t *dp_err,
+ dbus_uint32_t *dp_ret,
+ char **err_msg)
+{
+ acct_cb_t cb;
+
+ *dp_err = sss_mock_type(dbus_uint16_t);
+ *dp_ret = sss_mock_type(dbus_uint32_t);
+ *dp_ret = sss_mock_type(dbus_uint32_t);
+
+ cb = sss_mock_ptr_type(acct_cb_t);
+ if (cb) {
+ (cb)(sss_mock_ptr_type(void *));
+ }
+
+ return test_request_recv(req);
+}
+
+void mock_account_recv(uint16_t dp_err, uint32_t dp_ret, char *msg,
+ acct_cb_t acct_cb, void *pvt)
+{
+ will_return(sss_dp_get_account_recv, dp_err);
+ will_return(sss_dp_get_account_recv, dp_ret);
+ will_return(sss_dp_get_account_recv, msg);
+
+ will_return(sss_dp_get_account_recv, acct_cb);
+ if (acct_cb) {
+ will_return(sss_dp_get_account_recv, pvt);
+ }
+}
+
+void mock_account_recv_simple(void)
+{
+ return mock_account_recv(0, 0, NULL, NULL, NULL);
+}
+
+/* Mock subdomain requests */
+struct tevent_req *
+sss_dp_get_domains_send(TALLOC_CTX *mem_ctx,
+ struct resp_ctx *rctx,
+ bool force,
+ const char *hint)
+{
+ return test_req_succeed_send(mem_ctx, rctx->ev);
+}
+
+errno_t sss_dp_get_domains_recv(struct tevent_req *req)
+{
+ return test_request_recv(req);
+}
diff --git a/src/tests/cmocka/common_mock_resp.h b/src/tests/cmocka/common_mock_resp.h
new file mode 100644
index 00000000..5ddcc236
--- /dev/null
+++ b/src/tests/cmocka/common_mock_resp.h
@@ -0,0 +1,62 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ SSSD tests: Common utilities for tests that exercise domains
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __COMMON_MOCK_RESP_H_
+#define __COMMON_MOCK_RESP_H_
+
+#include "util/util.h"
+#include "responder/common/responder.h"
+#include "tests/cmocka/common_mock.h"
+
+/* Mock a responder context */
+struct resp_ctx *
+mock_rctx(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domains,
+ void *pvt_ctx);
+
+/* Mock a client context */
+struct cli_ctx *
+mock_cctx(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx);
+
+/* When mocking a module that calls sss_dp_get_account_{send,recv}
+ * requests, your test, when linked against this module, will call
+ * the mock functions instead. Then you can simulate results of the
+ * sss_dp_get_account_recv call by calling mock_account_recv.
+ *
+ * The mocked sss_sp_get_account_recv shall return the return values
+ * given with parameters dp_err, dp_ret and msg and optionally also call
+ * the acct_cb_t callback, if given with the pvt pointer as user data.
+ * The callback can for instance populate the cache, thus simulating
+ * Data Provider lookup.
+ *
+ * There is also even simpler wrapper called mock_account_recv_simple
+ * that just finishes the account request with a success.
+ */
+typedef int (*acct_cb_t)(void *);
+
+void mock_account_recv(uint16_t dp_err, uint32_t dp_ret, char *msg,
+ acct_cb_t acct_cb, void *pvt);
+
+void mock_account_recv_simple(void);
+
+#endif /* __COMMON_MOCK_RESP_H_ */
diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c
new file mode 100644
index 00000000..092e6803
--- /dev/null
+++ b/src/tests/cmocka/test_nss_srv.c
@@ -0,0 +1,512 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ SSSD tests: Common utilities for tests that exercise domains
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "responder/common/negcache.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_private.h"
+
+#define TESTS_PATH "tests_nss"
+#define TEST_CONF_DB "test_nss_conf.ldb"
+#define TEST_SYSDB_FILE "cache_nss_test.ldb"
+#define TEST_DOM_NAME "nss_test"
+#define TEST_ID_PROVIDER "ldap"
+
+struct nss_test_ctx {
+ struct sss_test_ctx *tctx;
+
+ struct resp_ctx *rctx;
+ struct cli_ctx *cctx;
+ struct sss_cmd_table *nss_cmds;
+ struct nss_ctx *nctx;
+
+ bool ncache_hit;
+};
+
+struct nss_test_ctx *nss_test_ctx;
+
+/* Mock NSS structure */
+struct nss_ctx *
+mock_nctx(TALLOC_CTX *mem_ctx)
+{
+ struct nss_ctx *nctx;
+ errno_t ret;
+
+ nctx = talloc_zero(mem_ctx, struct nss_ctx);
+ if (!nctx) {
+ return NULL;
+ }
+
+ ret = sss_ncache_init(nctx, &nctx->ncache);
+ if (ret != EOK) {
+ talloc_free(nctx);
+ return NULL;
+ }
+ nctx->neg_timeout = 10;
+
+ return nctx;
+}
+
+/* Mock reading requests from a client. Use values passed from mock
+ * instead
+ */
+void __real_sss_packet_get_body(struct sss_packet *packet,
+ uint8_t **body, size_t *blen);
+
+void __wrap_sss_packet_get_body(struct sss_packet *packet,
+ uint8_t **body, size_t *blen)
+{
+ enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call);
+
+ if (wtype == WRAP_CALL_REAL) {
+ return __real_sss_packet_get_body(packet, body, blen);
+ }
+
+ *body = sss_mock_ptr_type(uint8_t *);
+ *blen = strlen((const char *) *body)+1;
+ return;
+}
+
+/* Mock returning result to client. Terminate the unit test instead. */
+typedef int (*cmd_cb_fn_t)(uint8_t *, size_t );
+
+static void set_cmd_cb(cmd_cb_fn_t fn)
+{
+ will_return(__wrap_sss_cmd_done, fn);
+}
+
+void __wrap_sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+ struct sss_packet *packet = cctx->creq->out;
+ uint8_t *body;
+ size_t blen;
+ cmd_cb_fn_t check_cb;
+
+ check_cb = sss_mock_ptr_type(cmd_cb_fn_t);
+
+ __real_sss_packet_get_body(packet, &body, &blen);
+
+ nss_test_ctx->tctx->error = check_cb(body, blen);
+ nss_test_ctx->tctx->done = true;
+}
+
+enum sss_cli_command __wrap_sss_packet_get_cmd(struct sss_packet *packet)
+{
+ return sss_mock_type(enum sss_cli_command);
+}
+
+int __wrap_sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx)
+{
+ nss_test_ctx->tctx->done = true;
+ nss_test_ctx->tctx->error = ENOENT;
+ return EOK;
+}
+
+/* Intercept negative cache lookups */
+int __real_sss_ncache_check_user(struct sss_nc_ctx *ctx, int ttl,
+ struct sss_domain_info *dom, const char *name);
+
+int __wrap_sss_ncache_check_user(struct sss_nc_ctx *ctx, int ttl,
+ struct sss_domain_info *dom, const char *name)
+{
+ int ret;
+
+ ret = __real_sss_ncache_check_user(ctx, ttl, dom, name);
+ if (ret == EEXIST) {
+ nss_test_ctx->ncache_hit = true;
+ }
+ return ret;
+}
+
+/* Mock input from the client library */
+static void mock_input_user(const char *username)
+{
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_WRAPPER);
+ will_return(__wrap_sss_packet_get_body, username);
+}
+
+static void mock_fill_user(void)
+{
+ /* One packet for the entry and one for num entries */
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+}
+
+static int parse_user_packet(uint8_t *body, size_t blen, struct passwd *pwd)
+{
+ size_t rp = 2 * sizeof(uint32_t);
+
+ SAFEALIGN_COPY_UINT32(&pwd->pw_uid, body+rp, &rp);
+ SAFEALIGN_COPY_UINT32(&pwd->pw_gid, body+rp, &rp);
+
+ /* Sequence of null terminated strings (name, passwd, gecos, dir, shell) */
+ pwd->pw_name = (char *) body+rp;
+ rp += strlen(pwd->pw_name) + 1;
+ if (rp >= blen) return EINVAL;
+
+ pwd->pw_gecos = (char *) body+rp;
+ rp += strlen(pwd->pw_gecos) + 1;
+ if (rp >= blen) return EINVAL;
+
+ pwd->pw_dir = (char *) body+rp;
+ rp += strlen(pwd->pw_dir) + 1;
+ if (rp >= blen) return EINVAL;
+
+ pwd->pw_shell = (char *) body+rp;
+ return EOK;
+}
+
+/* ====================== The tests =============================== */
+
+/* Check getting cached and valid user from cache. Account callback will
+ * not be called and test_nss_getpwnam_check will make sure the user is
+ * the same as the test entered before starting
+ */
+static int test_nss_getpwnam_check(uint8_t *body, size_t blen)
+{
+ struct passwd pwd;
+ errno_t ret;
+
+ ret = parse_user_packet(body, blen, &pwd);
+ assert_int_equal(ret, EOK);
+
+ assert_int_equal(pwd.pw_uid, 123);
+ assert_int_equal(pwd.pw_gid, 456);
+ assert_string_equal(pwd.pw_name, "testuser");
+ assert_string_equal(pwd.pw_shell, "/bin/sh");
+ return EOK;
+}
+
+void test_nss_getpwnam(void **state)
+{
+ errno_t ret;
+
+ /* Prime the cache with a valid user */
+ ret = sysdb_add_user(nss_test_ctx->tctx->sysdb,
+ nss_test_ctx->tctx->dom,
+ "testuser", 123, 456, "test user",
+ "/home/testuser", "/bin/sh", NULL,
+ NULL, 300, 0);
+ assert_int_equal(ret, EOK);
+
+ mock_input_user("testuser");
+ will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+ mock_fill_user();
+
+ /* Query for that user, call a callback when command finishes */
+ set_cmd_cb(test_nss_getpwnam_check);
+ ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+ nss_test_ctx->nss_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(nss_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+}
+
+/* Test that searching for a nonexistant user yields ENOENT.
+ * Account callback will be called
+ */
+void test_nss_getpwnam_neg(void **state)
+{
+ errno_t ret;
+
+ mock_input_user("testuser_neg");
+ mock_account_recv_simple();
+
+ assert_true(nss_test_ctx->ncache_hit == false);
+
+ ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+ nss_test_ctx->nss_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with ENOENT */
+ ret = test_ev_loop(nss_test_ctx->tctx);
+ assert_int_equal(ret, ENOENT);
+ assert_true(nss_test_ctx->ncache_hit == false);
+
+ /* Test that subsequent search for a nonexistent user yields
+ * ENOENT and Account callback is not called, on the other hand
+ * the ncache functions will be called
+ */
+ nss_test_ctx->tctx->done = false;
+
+ mock_input_user("testuser_neg");
+ ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+ nss_test_ctx->nss_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with ENOENT */
+ ret = test_ev_loop(nss_test_ctx->tctx);
+ assert_int_equal(ret, ENOENT);
+ /* Negative cache was hit this time */
+ assert_true(nss_test_ctx->ncache_hit == true);
+}
+
+static int test_nss_getpwnam_search_acct_cb(void *pvt)
+{
+ errno_t ret;
+ struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
+
+ ret = sysdb_add_user(ctx->tctx->sysdb,
+ ctx->tctx->dom,
+ "testuser_search", 567, 890, "test search",
+ "/home/testsearch", "/bin/sh", NULL,
+ NULL, 300, 0);
+ assert_int_equal(ret, EOK);
+
+ return EOK;
+}
+
+static int test_nss_getpwnam_search_check(uint8_t *body, size_t blen)
+{
+ struct passwd pwd;
+ errno_t ret;
+
+ ret = parse_user_packet(body, blen, &pwd);
+ assert_int_equal(ret, EOK);
+
+ assert_int_equal(pwd.pw_uid, 567);
+ assert_int_equal(pwd.pw_gid, 890);
+ assert_string_equal(pwd.pw_name, "testuser_search");
+ assert_string_equal(pwd.pw_shell, "/bin/sh");
+ return EOK;
+}
+
+void test_nss_getpwnam_search(void **state)
+{
+ errno_t ret;
+ struct ldb_result *res;
+
+ mock_input_user("testuser_search");
+ mock_account_recv(0, 0, NULL, test_nss_getpwnam_search_acct_cb, nss_test_ctx);
+ will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+ mock_fill_user();
+ set_cmd_cb(test_nss_getpwnam_search_check);
+
+ ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+ nss_test_ctx->tctx->dom, "testuser_search",
+ &res);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(res->count, 0);
+
+ ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+ nss_test_ctx->nss_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(nss_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+ /* test_nss_getpwnam_search_check will check the user attributes */
+ ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+ nss_test_ctx->tctx->dom, "testuser_search",
+ &res);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(res->count, 1);
+}
+
+/* Test that searching for a user that is expired in the cache goes to the DP
+ * which updates the record and the NSS responder returns the updated record
+ *
+ * The user's shell attribute is updated.
+ */
+static int test_nss_getpwnam_update_acct_cb(void *pvt)
+{
+ errno_t ret;
+ struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
+
+ ret = sysdb_store_user(ctx->tctx->sysdb,
+ ctx->tctx->dom,
+ "testuser_update", NULL, 10, 11, "test user",
+ "/home/testuser", "/bin/ksh", NULL,
+ NULL, NULL, 300, 0);
+ assert_int_equal(ret, EOK);
+
+ return EOK;
+}
+
+static int test_nss_getpwnam_update_check(uint8_t *body, size_t blen)
+{
+ struct passwd pwd;
+ errno_t ret;
+
+ ret = parse_user_packet(body, blen, &pwd);
+ assert_int_equal(ret, EOK);
+
+ assert_int_equal(pwd.pw_uid, 10);
+ assert_int_equal(pwd.pw_gid, 11);
+ assert_string_equal(pwd.pw_name, "testuser_update");
+ assert_string_equal(pwd.pw_shell, "/bin/ksh");
+ return EOK;
+}
+
+void test_nss_getpwnam_update(void **state)
+{
+ errno_t ret;
+ struct ldb_result *res;
+ const char *shell;
+
+ /* Prime the cache with a valid but expired user */
+ ret = sysdb_add_user(nss_test_ctx->tctx->sysdb,
+ nss_test_ctx->tctx->dom,
+ "testuser_update", 10, 11, "test user",
+ "/home/testuser", "/bin/sh", NULL,
+ NULL, 1, 1);
+ assert_int_equal(ret, EOK);
+
+ /* Mock client input */
+ mock_input_user("testuser_update");
+ /* Mock client command */
+ will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+ /* Call this function when user is updated by the mock DP request */
+ mock_account_recv(0, 0, NULL, test_nss_getpwnam_update_acct_cb, nss_test_ctx);
+ /* Call this function to check what the responder returned to the client */
+ set_cmd_cb(test_nss_getpwnam_update_check);
+ /* Mock output buffer */
+ mock_fill_user();
+
+ /* Fire the command */
+ ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+ nss_test_ctx->nss_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(nss_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+ /* Check the user was updated in the cache */
+ ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+ nss_test_ctx->tctx->dom, "testuser_update",
+ &res);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(res->count, 1);
+
+ shell = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SHELL, NULL);
+ assert_string_equal(shell, "/bin/ksh");
+}
+
+/* Testsuite setup and teardown */
+void nss_test_setup(void **state)
+{
+ errno_t ret;
+ struct sss_test_conf_param params[] = {
+ { "enumerate", "false" },
+ { NULL, NULL }, /* Sentinel */
+ };
+
+ nss_test_ctx = talloc_zero(NULL, struct nss_test_ctx);
+ assert_non_null(nss_test_ctx);
+
+ nss_test_ctx->tctx = create_dom_test_ctx(nss_test_ctx, TESTS_PATH, TEST_CONF_DB,
+ TEST_SYSDB_FILE, TEST_DOM_NAME,
+ TEST_ID_PROVIDER, params);
+ assert_non_null(nss_test_ctx->tctx);
+
+ nss_test_ctx->nss_cmds = get_nss_cmds();
+ assert_non_null(nss_test_ctx->nss_cmds);
+
+ /* FIXME - perhaps this should be folded into sssd_domain_init or stricty
+ * used together
+ */
+ ret = sss_names_init(nss_test_ctx, nss_test_ctx->tctx->confdb,
+ TEST_DOM_NAME, &nss_test_ctx->tctx->dom->names);
+ assert_int_equal(ret, EOK);
+
+ /* Initialize the NSS responder */
+ nss_test_ctx->nctx = mock_nctx(nss_test_ctx);
+ assert_non_null(nss_test_ctx->nctx);
+
+ nss_test_ctx->rctx = mock_rctx(nss_test_ctx, nss_test_ctx->tctx->ev,
+ nss_test_ctx->tctx->dom, nss_test_ctx->nctx);
+ assert_non_null(nss_test_ctx->rctx);
+
+ /* Create client context */
+ nss_test_ctx->cctx = mock_cctx(nss_test_ctx, nss_test_ctx->rctx);
+ assert_non_null(nss_test_ctx->cctx);
+}
+
+void nss_test_teardown(void **state)
+{
+ talloc_free(nss_test_ctx);
+}
+
+int main(int argc, const char *argv[])
+{
+ int rv;
+ int no_cleanup = 0;
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+ _("Do not delete the test database after a test run"), NULL },
+ POPT_TABLEEND
+ };
+
+ const UnitTest tests[] = {
+ unit_test_setup_teardown(test_nss_getpwnam,
+ nss_test_setup, nss_test_teardown),
+ unit_test_setup_teardown(test_nss_getpwnam_neg,
+ nss_test_setup, nss_test_teardown),
+ unit_test_setup_teardown(test_nss_getpwnam_search,
+ nss_test_setup, nss_test_teardown),
+ unit_test_setup_teardown(test_nss_getpwnam_update,
+ nss_test_setup, nss_test_teardown),
+ };
+
+ /* Set debug level to invalid value so we can deside if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ DEBUG_INIT(debug_level);
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old db to be sure */
+ tests_set_cwd();
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+ test_dom_suite_setup(TESTS_PATH);
+
+ rv = run_tests(tests);
+ if (rv == 0 && !no_cleanup) {
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+ }
+ return rv;
+}