/*
   Unix SMB/CIFS implementation.

   DRSUAPI schemaInfo unit tests

   Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2010

   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 "includes.h"
#include "system/filesys.h"
#include "torture/smbtorture.h"
#include "dsdb/samdb/samdb.h"
#include "dsdb/samdb/ldb_modules/util.h"
#include "ldb_wrap.h"
#include <ldb_module.h>
#include "torture/rpc/drsuapi.h"
#include "librpc/ndr/libndr.h"
#include "param/param.h"
#include "torture/drs/proto.h"
#include "torture/drs/proto.h"


/**
 * schemaInfo to init ldb context with
 *   Rev:  0
 *   GUID: 00000000-0000-0000-0000-000000000000
 */
#define SCHEMA_INFO_INIT_STR		"FF0000000000000000000000000000000000000000"

/**
 * Default schema_info string to be used for testing
 *   Rev:  01
 *   GUID: 071c82fd-45c7-4351-a3db-51f75a630a7f
 */
#define SCHEMA_INFO_DEFAULT_STR 	"FF00000001FD821C07C7455143A3DB51F75A630A7F"

/**
 * Schema info data to test with
 */
struct schemainfo_data {
	DATA_BLOB 	ndr_blob;
	struct dsdb_schema_info schi;
	WERROR		werr_expected;
	bool 		test_both_ways;
};

/**
 * Schema info test data in human-readable format (... kind of)
 */
static const struct {
	const char 	*schema_info_str;
	uint32_t	revision;
	const char	*guid_str;
	WERROR		werr_expected;
	bool 		test_both_ways;
} _schemainfo_test_data[] = {
	{
		.schema_info_str = "FF0000000000000000000000000000000000000000",
		.revision = 0,
		.guid_str = "00000000-0000-0000-0000-000000000000",
		.werr_expected = WERR_OK,
		.test_both_ways = true
	},
	{
		.schema_info_str = "FF00000001FD821C07C7455143A3DB51F75A630A7F",
		.revision = 1,
		.guid_str = "071c82fd-45c7-4351-a3db-51f75a630a7f",
		.werr_expected = WERR_OK,
		.test_both_ways = true
	},
	{
		.schema_info_str = "FFFFFFFFFFFD821C07C7455143A3DB51F75A630A7F",
		.revision = 0xFFFFFFFF,
		.guid_str = "071c82fd-45c7-4351-a3db-51f75a630a7f",
		.werr_expected = WERR_OK,
		.test_both_ways = true
	},
	{ /* len == 21 */
		.schema_info_str = "FF00000001FD821C07C7455143A3DB51F75A630A7F00",
		.revision = 1,
		.guid_str = "071c82fd-45c7-4351-a3db-51f75a630a7f",
		.werr_expected = WERR_INVALID_PARAMETER,
		.test_both_ways = false
	},
	{ /* marker == FF */
		.schema_info_str = "AA00000001FD821C07C7455143A3DB51F75A630A7F",
		.revision = 1,
		.guid_str = "071c82fd-45c7-4351-a3db-51f75a630a7f",
		.werr_expected = WERR_INVALID_PARAMETER,
		.test_both_ways = false
	}
};

/**
 * Private data to be shared among all test in Test case
 */
struct drsut_schemainfo_data {
	struct ldb_context *ldb;
	struct ldb_module  *ldb_module;
	struct dsdb_schema *schema;

	/* Initial schemaInfo set in ldb to test with */
	struct dsdb_schema_info *schema_info;

	uint32_t test_data_count;
	struct schemainfo_data *test_data;
};

/**
 * torture macro to assert for equal dsdb_schema_info's
 */
#define torture_assert_schema_info_equal(torture_ctx,got,expected,cmt)\
	do { const struct dsdb_schema_info *__got = (got), *__expected = (expected); \
	if (__got->revision != __expected->revision) { \
		torture_result(torture_ctx, TORTURE_FAIL, \
			       __location__": "#got".revision %d did not match "#expected".revision %d: %s", \
			       (int)__got->revision, (int)__expected->revision, cmt); \
		return false; \
	} \
	if (!GUID_equal(&__got->invocation_id, &__expected->invocation_id)) { \
		torture_result(torture_ctx, TORTURE_FAIL, \
			       __location__": "#got".invocation_id did not match "#expected".invocation_id: %s", cmt); \
		return false; \
	} \
	} while(0)

/*
 * forward declaration for internal functions
 */
static bool _drsut_ldb_schema_info_reset(struct torture_context *tctx,
					 struct ldb_context *ldb,
					 const char *schema_info_str,
					 bool in_setup);


/**
 * Creates dsdb_schema_info object based on NDR data
 * passed as hex string
 */
static bool _drsut_schemainfo_new(struct torture_context *tctx,
				  const char *schema_info_str, struct dsdb_schema_info **_si)
{
	WERROR werr;
	DATA_BLOB blob;

	blob = strhex_to_data_blob(tctx, schema_info_str);
	if (!blob.data) {
		torture_comment(tctx, "Not enough memory!\n");
		return false;
	}

	werr = dsdb_schema_info_from_blob(&blob, tctx, _si);
	if (!W_ERROR_IS_OK(werr)) {
		torture_comment(tctx,
		                "Failed to create dsdb_schema_info object for %s: %s",
		                schema_info_str,
		                win_errstr(werr));
		return false;
	}

	data_blob_free(&blob);

	return true;
}

/**
 * Creates dsdb_schema_info object based on predefined data
 * Function is public as it is intended to be used by other
 * tests (e.g. prefixMap tests)
 */
bool drsut_schemainfo_new(struct torture_context *tctx, struct dsdb_schema_info **_si)
{
	return _drsut_schemainfo_new(tctx, SCHEMA_INFO_DEFAULT_STR, _si);
}


/*
 * Tests dsdb_schema_info_new() and dsdb_schema_info_blob_new()
 */
static bool test_dsdb_schema_info_new(struct torture_context *tctx,
				      struct drsut_schemainfo_data *priv)
{
	WERROR werr;
	DATA_BLOB ndr_blob;
	DATA_BLOB ndr_blob_expected;
	struct dsdb_schema_info *schi;
	TALLOC_CTX *mem_ctx;

	mem_ctx = talloc_new(priv);
	torture_assert(tctx, mem_ctx, "Not enough memory!");
	ndr_blob_expected = strhex_to_data_blob(mem_ctx, SCHEMA_INFO_INIT_STR);
	torture_assert(tctx, ndr_blob_expected.data, "Not enough memory!");

	werr = dsdb_schema_info_new(mem_ctx, &schi);
	torture_assert_werr_ok(tctx, werr, "dsdb_schema_info_new() failed");
	torture_assert_int_equal(tctx, schi->revision, 0,
				 "dsdb_schema_info_new() creates schemaInfo with invalid revision");
	torture_assert(tctx, GUID_all_zero(&schi->invocation_id),
			"dsdb_schema_info_new() creates schemaInfo with not ZERO GUID");

	werr = dsdb_schema_info_blob_new(mem_ctx, &ndr_blob);
	torture_assert_werr_ok(tctx, werr, "dsdb_schema_info_blob_new() failed");
	torture_assert_data_blob_equal(tctx, ndr_blob, ndr_blob_expected,
				       "dsdb_schema_info_blob_new() returned invalid blob");

	talloc_free(mem_ctx);
	return true;
}

/*
 * Tests dsdb_schema_info_from_blob()
 */
static bool test_dsdb_schema_info_from_blob(struct torture_context *tctx,
					    struct drsut_schemainfo_data *priv)
{
	int i;
	WERROR werr;
	char *msg;
	struct dsdb_schema_info *schema_info;
	TALLOC_CTX *mem_ctx;

	mem_ctx = talloc_new(priv);
	torture_assert(tctx, mem_ctx, "Not enough memory!");

	for (i = 0; i < priv->test_data_count; i++) {
		struct schemainfo_data *data = &priv->test_data[i];

		msg = talloc_asprintf(tctx, "dsdb_schema_info_from_blob() [%d]-[%s]",
		                      i, _schemainfo_test_data[i].schema_info_str);

		werr = dsdb_schema_info_from_blob(&data->ndr_blob, mem_ctx, &schema_info);
		torture_assert_werr_equal(tctx, werr, data->werr_expected, msg);

		/* test returned data */
		if (W_ERROR_IS_OK(werr)) {
			torture_assert_schema_info_equal(tctx,
			                                 schema_info, &data->schi,
			                                 "after dsdb_schema_info_from_blob() call");
		}
	}

	talloc_free(mem_ctx);

	return true;
}

/*
 * Tests dsdb_blob_from_schema_info()
 */
static bool test_dsdb_blob_from_schema_info(struct torture_context *tctx,
					    struct drsut_schemainfo_data *priv)
{
	int i;
	WERROR werr;
	char *msg;
	DATA_BLOB ndr_blob;
	TALLOC_CTX *mem_ctx;

	mem_ctx = talloc_new(priv);
	torture_assert(tctx, mem_ctx, "Not enough memory!");

	for (i = 0; i < priv->test_data_count; i++) {
		struct schemainfo_data *data = &priv->test_data[i];

		/* not all test are valid reverse type of conversion */
		if (!data->test_both_ways) {
			continue;
		}

		msg = talloc_asprintf(tctx, "dsdb_blob_from_schema_info() [%d]-[%s]",
		                      i, _schemainfo_test_data[i].schema_info_str);

		werr = dsdb_blob_from_schema_info(&data->schi, mem_ctx, &ndr_blob);
		torture_assert_werr_equal(tctx, werr, data->werr_expected, msg);

		/* test returned data */
		if (W_ERROR_IS_OK(werr)) {
			torture_assert_data_blob_equal(tctx,
			                               ndr_blob, data->ndr_blob,
			                               "dsdb_blob_from_schema_info()");
		}
	}

	talloc_free(mem_ctx);

	return true;
}

static bool test_dsdb_schema_info_cmp(struct torture_context *tctx,
				      struct drsut_schemainfo_data *priv)
{
	DATA_BLOB blob;
	struct drsuapi_DsReplicaOIDMapping_Ctr *ctr;
	struct dsdb_schema_info schema_info;

	ctr = talloc_zero(priv, struct drsuapi_DsReplicaOIDMapping_Ctr);
	torture_assert(tctx, ctr, "Not enough memory!");

	/* not enough elements */
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_INVALID_PARAMETER,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* an empty element for schemaInfo */
	ctr->num_mappings = 1;
	ctr->mappings = talloc_zero_array(ctr, struct drsuapi_DsReplicaOIDMapping, 1);
	torture_assert(tctx, ctr->mappings, "Not enough memory!");
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_INVALID_PARAMETER,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with invalid schemaInfo - length != 21 */
	blob = strhex_to_data_blob(ctr, "FF00000001FD821C07C7455143A3DB51F75A630A7F00");
	torture_assert(tctx, blob.data, "Not enough memory!");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_INVALID_PARAMETER,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with invalid schemaInfo - marker != 0xFF */
	blob = strhex_to_data_blob(ctr, "AA00000001FD821C07C7455143A3DB51F75A630A7F");
	torture_assert(tctx, blob.data, "Not enough memory!");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_INVALID_PARAMETER,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but older one should be ok */
	blob = strhex_to_data_blob(ctr, "FF0000000000000000000000000000000000000000");
	torture_assert(tctx, blob.data, "Not enough memory!");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_OK,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with correct schemaInfo, but invalid ATTID */
	schema_info = *priv->schema->schema_info;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].id_prefix	= 1;
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_INVALID_PARAMETER,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo */
	ctr->mappings[0].id_prefix	= 0;
	torture_assert_werr_ok(tctx,
			       dsdb_schema_info_cmp(priv->schema, ctr),
			       "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but older revision */
	schema_info = *priv->schema->schema_info;
	schema_info.revision -= 1;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_OK,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but newer revision */
	schema_info = *priv->schema->schema_info;
	schema_info.revision += 1;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_DS_DRA_SCHEMA_MISMATCH,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but newer revision and other invocationId */
	schema_info = *priv->schema->schema_info;
	schema_info.revision += 1;
	schema_info.invocation_id.time_mid += 1;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_DS_DRA_SCHEMA_MISMATCH,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but older revision and other invocationId */
	schema_info = *priv->schema->schema_info;
	schema_info.revision -= 1;
	schema_info.invocation_id.time_mid += 1;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_OK,
				  "dsdb_schema_info_cmp(): unexpected result");

	/* test with valid schemaInfo, but same revision and other invocationId */
	schema_info = *priv->schema->schema_info;
	schema_info.invocation_id.time_mid += 1;
	torture_assert_werr_ok(tctx,
		dsdb_blob_from_schema_info(&schema_info, tctx, &blob),
		"dsdb_blob_from_schema_info() failed");
	ctr->mappings[0].oid.length     = blob.length;
	ctr->mappings[0].oid.binary_oid = blob.data;
	torture_assert_werr_equal(tctx,
				  dsdb_schema_info_cmp(priv->schema, ctr),
				  WERR_DS_DRA_SCHEMA_CONFLICT,
				  "dsdb_schema_info_cmp(): unexpected result");

	talloc_free(ctr);
	return true;
}

/*
 * Tests dsdb_module_schema_info_blob_read()
 *   and dsdb_module_schema_info_blob_write()
 */
static bool test_dsdb_module_schema_info_blob_rw(struct torture_context *tctx,
						struct drsut_schemainfo_data *priv)
{
	int ldb_err;
	DATA_BLOB blob_write;
	DATA_BLOB blob_read;

	/* reset schmeInfo to know value */
	torture_assert(tctx,
		       _drsut_ldb_schema_info_reset(tctx, priv->ldb, SCHEMA_INFO_INIT_STR, false),
		       "_drsut_ldb_schema_info_reset() failed");

	/* write tests' default schemaInfo */
	blob_write = strhex_to_data_blob(priv, SCHEMA_INFO_DEFAULT_STR);
	torture_assert(tctx, blob_write.data, "Not enough memory!");

	ldb_err = dsdb_module_schema_info_blob_write(priv->ldb_module,
						     DSDB_FLAG_TOP_MODULE,
						     &blob_write, NULL);
	torture_assert_int_equal(tctx, ldb_err, LDB_SUCCESS, "dsdb_module_schema_info_blob_write() failed");

	ldb_err = dsdb_module_schema_info_blob_read(priv->ldb_module, DSDB_FLAG_TOP_MODULE,
						    priv, &blob_read, NULL);
	torture_assert_int_equal(tctx, ldb_err, LDB_SUCCESS, "dsdb_module_schema_info_blob_read() failed");

	/* check if we get what we wrote */
	torture_assert_data_blob_equal(tctx, blob_read, blob_write,
				       "Write/Read of schemeInfo blob failed");

	return true;
}

/*
 * Tests dsdb_schema_update_schema_info()
 */
static bool test_dsdb_module_schema_info_update(struct torture_context *tctx,
						struct drsut_schemainfo_data *priv)
{
	int ldb_err;
	WERROR werr;
	DATA_BLOB blob;
	struct dsdb_schema_info *schema_info;

	/* reset schmeInfo to know value */
	torture_assert(tctx,
		       _drsut_ldb_schema_info_reset(tctx, priv->ldb, SCHEMA_INFO_INIT_STR, false),
		       "_drsut_ldb_schema_info_reset() failed");

	ldb_err = dsdb_module_schema_info_update(priv->ldb_module,
						 priv->schema,
						 DSDB_FLAG_TOP_MODULE | DSDB_FLAG_AS_SYSTEM, NULL);
	torture_assert_int_equal(tctx, ldb_err, LDB_SUCCESS, "dsdb_module_schema_info_update() failed");

	/* get updated schemaInfo */
	ldb_err = dsdb_module_schema_info_blob_read(priv->ldb_module, DSDB_FLAG_TOP_MODULE,
						    priv, &blob, NULL);
	torture_assert_int_equal(tctx, ldb_err, LDB_SUCCESS, "dsdb_module_schema_info_blob_read() failed");

	werr = dsdb_schema_info_from_blob(&blob, priv, &schema_info);
	torture_assert_werr_ok(tctx, werr, "dsdb_schema_info_from_blob() failed");

	/* check against default schema_info */
	torture_assert_schema_info_equal(tctx, schema_info, priv->schema_info,
					  "schemaInfo attribute no updated correctly");

	return true;
}


/**
 * Reset schemaInfo record to know value
 */
static bool _drsut_ldb_schema_info_reset(struct torture_context *tctx,
					 struct ldb_context *ldb,
					 const char *schema_info_str,
					 bool in_setup)
{
	bool bret = true;
	int ldb_err;
	DATA_BLOB blob;
	struct ldb_message *msg;
	TALLOC_CTX *mem_ctx = talloc_new(tctx);

	blob = strhex_to_data_blob(mem_ctx, schema_info_str);
	torture_assert_goto(tctx, blob.data, bret, DONE, "Not enough memory!");

	msg = ldb_msg_new(mem_ctx);
	torture_assert_goto(tctx, msg, bret, DONE, "Not enough memory!");

	msg->dn = ldb_get_schema_basedn(ldb);
	ldb_err = ldb_msg_add_value(msg, "schemaInfo", &blob, NULL);
	torture_assert_int_equal_goto(tctx, ldb_err, LDB_SUCCESS, bret, DONE,
				      "ldb_msg_add_value() failed");

	if (in_setup) {
		ldb_err = ldb_add(ldb, msg);
	} else {
		ldb_err = dsdb_replace(ldb, msg, DSDB_MODIFY_PERMISSIVE);
	}
	torture_assert_int_equal_goto(tctx, ldb_err, LDB_SUCCESS, bret, DONE,
				      "dsdb_replace() failed");

DONE:
	talloc_free(mem_ctx);
	return bret;
}

/**
 * Prepare temporary LDB and opens it
 */
static bool _drsut_ldb_setup(struct torture_context *tctx, struct drsut_schemainfo_data *priv)
{
	int ldb_err;
	char *ldb_url;
	bool bret = true;
	char *tempdir = NULL;
	NTSTATUS status;
	TALLOC_CTX* mem_ctx;

	mem_ctx = talloc_new(priv);
	torture_assert(tctx, mem_ctx, "Not enough memory!");

	status = torture_temp_dir(tctx, "drs_", &tempdir);
	torture_assert_ntstatus_ok_goto(tctx, status, bret, DONE, "creating temp dir");

	ldb_url = talloc_asprintf(priv, "%s/drs_schemainfo.ldb", tempdir);
	torture_assert_goto(tctx, ldb_url, bret, DONE, "Not enough memory!");

	/* create LDB */
	priv->ldb = ldb_wrap_connect(priv, tctx->ev, tctx->lp_ctx,
	                             ldb_url, NULL, NULL, 0);
	torture_assert_goto(tctx, priv->ldb, bret, DONE,  "ldb_wrap_connect() failed");

	/* set some schemaNamingContext */
	ldb_err = ldb_set_opaque(priv->ldb,
				 "schemaNamingContext",
				 ldb_dn_new(priv->ldb, priv->ldb, "CN=Schema,CN=Config"));
	torture_assert_int_equal_goto(tctx, ldb_err, LDB_SUCCESS, bret, DONE,
				      "ldb_set_opaque() failed");

	/* add schemaInfo attribute so tested layer could work properly */
	torture_assert_goto(tctx,
			    _drsut_ldb_schema_info_reset(tctx, priv->ldb, SCHEMA_INFO_INIT_STR, true),
			    bret, DONE,
			    "_drsut_ldb_schema_info_reset() failed");

DONE:
	talloc_free(tempdir);
	talloc_free(mem_ctx);
	return bret;
}

/*
 * Setup/Teardown for test case
 */
static bool torture_drs_unit_schemainfo_setup(struct torture_context *tctx,
					      struct drsut_schemainfo_data **_priv)
{
	int i;
	int ldb_err;
	NTSTATUS status;
	DATA_BLOB ndr_blob;
	struct GUID guid;
	struct drsut_schemainfo_data *priv;

	priv = talloc_zero(tctx, struct drsut_schemainfo_data);
	torture_assert(tctx, priv, "Not enough memory!");

	/* returned allocated pointer here
	 * teardown() will be called even in case of failure,
	 * so we'll get a changes to clean up  */
	*_priv = priv;

	/* create initial schemaInfo */
	torture_assert(tctx,
		       _drsut_schemainfo_new(tctx, SCHEMA_INFO_DEFAULT_STR, &priv->schema_info),
		       "Failed to create schema_info test object");

	/* create data to test with */
	priv->test_data_count = ARRAY_SIZE(_schemainfo_test_data);
	priv->test_data = talloc_array(tctx, struct schemainfo_data, priv->test_data_count);

	for (i = 0; i < ARRAY_SIZE(_schemainfo_test_data); i++) {
		struct schemainfo_data *data = &priv->test_data[i];

		ndr_blob = strhex_to_data_blob(priv,
		                               _schemainfo_test_data[i].schema_info_str);
		torture_assert(tctx, ndr_blob.data, "Not enough memory!");

		status = GUID_from_string(_schemainfo_test_data[i].guid_str, &guid);
		torture_assert_ntstatus_ok(tctx, status,
					   talloc_asprintf(tctx,
					                   "GUID_from_string() failed for %s",
					                   _schemainfo_test_data[i].guid_str));

		data->ndr_blob           = ndr_blob;
		data->schi.invocation_id = guid;
		data->schi.revision      = _schemainfo_test_data[i].revision;
		data->werr_expected      = _schemainfo_test_data[i].werr_expected;
		data->test_both_ways     = _schemainfo_test_data[i].test_both_ways;

	}

	/* create temporary LDB and populate with data */
	if (!_drsut_ldb_setup(tctx, priv)) {
		return false;
	}

	/* create ldb_module mockup object */
	priv->ldb_module = ldb_module_new(priv, priv->ldb, "schemaInfo_test_module", NULL);
	torture_assert(tctx, priv->ldb_module, "Not enough memory!");

	/* create schema mockup object */
	priv->schema = dsdb_new_schema(priv);

	/* set schema_info in dsdb_schema for testing */
	torture_assert(tctx,
		       _drsut_schemainfo_new(tctx, SCHEMA_INFO_DEFAULT_STR, &priv->schema->schema_info),
		       "Failed to create schema_info test object");

	/* pre-cache invocationId for samdb_ntds_invocation_id()
	 * to work with our mock ldb */
	ldb_err = ldb_set_opaque(priv->ldb, "cache.invocation_id",
	                         &priv->schema_info->invocation_id);
	torture_assert_int_equal(tctx, ldb_err, LDB_SUCCESS, "ldb_set_opaque() failed");

	/* Perform all tests in transactions so that
	 * underlying modify calls not to fail */
	ldb_err = ldb_transaction_start(priv->ldb);
	torture_assert_int_equal(tctx,
				 ldb_err,
				 LDB_SUCCESS,
				 "ldb_transaction_start() failed");

	return true;
}

static bool torture_drs_unit_schemainfo_teardown(struct torture_context *tctx,
						 struct drsut_schemainfo_data *priv)
{
	int ldb_err;

	/* commit pending transaction so we will
	 * be able to check what LDB state is */
	ldb_err = ldb_transaction_commit(priv->ldb);
	if (ldb_err != LDB_SUCCESS) {
		torture_comment(tctx, "ldb_transaction_commit() - %s (%s)",
		                ldb_strerror(ldb_err),
		                ldb_errstring(priv->ldb));
	}

	talloc_free(priv);

	return true;
}

/**
 * Test case initialization for
 * drs.unit.schemaInfo
 */
struct torture_tcase * torture_drs_unit_schemainfo(struct torture_suite *suite)
{
	typedef bool (*pfn_setup)(struct torture_context *, void **);
	typedef bool (*pfn_teardown)(struct torture_context *, void *);
	typedef bool (*pfn_run)(struct torture_context *, void *);

	struct torture_tcase * tc = torture_suite_add_tcase(suite, "schemaInfo");

	torture_tcase_set_fixture(tc,
				  (pfn_setup)torture_drs_unit_schemainfo_setup,
				  (pfn_teardown)torture_drs_unit_schemainfo_teardown);

	tc->description = talloc_strdup(tc, "Unit tests for DRSUAPI::schemaInfo implementation");

	torture_tcase_add_simple_test(tc, "dsdb_schema_info_new",
				      (pfn_run)test_dsdb_schema_info_new);
	torture_tcase_add_simple_test(tc, "dsdb_schema_info_from_blob",
				      (pfn_run)test_dsdb_schema_info_from_blob);
	torture_tcase_add_simple_test(tc, "dsdb_blob_from_schema_info",
				      (pfn_run)test_dsdb_blob_from_schema_info);
	torture_tcase_add_simple_test(tc, "dsdb_schema_info_cmp",
				      (pfn_run)test_dsdb_schema_info_cmp);
	torture_tcase_add_simple_test(tc, "dsdb_module_schema_info_blob read|write",
				      (pfn_run)test_dsdb_module_schema_info_blob_rw);
	torture_tcase_add_simple_test(tc, "dsdb_module_schema_info_update",
				      (pfn_run)test_dsdb_module_schema_info_update);


	return tc;
}
