#include "sqliteInt.h" #include "unity.h" #include #include #include /* Wrapper provided for static function under test */ extern void test_sqlite3ErrorIfNotEmpty( Parse *pParse, const char *zDb, const char *zTab, const char *zErr ); static sqlite3 *gDb = NULL; /* Helpers */ static void exec_or_fail(const char *zSql){ char *zErr = NULL; int rc = sqlite3_exec(gDb, zSql, 0, 0, &zErr); if( rc!=SQLITE_OK ){ fprintf(stderr, "SQL failed: %s\n rc=%d err=%s\n", zSql, rc, zErr?zErr:"(null)"); } TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "exec_or_fail SQL error"); if( zErr ) sqlite3_free(zErr); } static void init_parse(Parse *p){ memset(p, 0, sizeof(*p)); p->db = gDb; } static void cleanup_parse(Parse *p){ if( p->pVdbe ){ sqlite3VdbeDelete(p->pVdbe); p->pVdbe = NULL; } if( p->zErrMsg ){ sqlite3_free(p->zErrMsg); p->zErrMsg = NULL; } } /* Unity required hooks */ void setUp(void) { int rc = sqlite3_open(":memory:", &gDb); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); } void tearDown(void) { if( gDb ){ int rc = sqlite3_close(gDb); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); gDb = NULL; } } /* Tests */ void test_sqlite3ErrorIfNotEmpty_empty_table_executes_without_error(void){ /* Setup schema: empty table */ exec_or_fail("CREATE TABLE t(a)"); /* Call target wrapper to build nested parse (ensures it handles normal names) */ Parse s = {0}; init_parse(&s); const char *zErr = "table is not empty"; test_sqlite3ErrorIfNotEmpty(&s, "main", "t", zErr); cleanup_parse(&s); /* Validate functional intent by running the equivalent SQL directly */ char *zSql = sqlite3_mprintf("SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", zErr, "main", "t"); TEST_ASSERT_NOT_NULL(zSql); char *zExecErr = NULL; int rc = sqlite3_exec(gDb, zSql, 0, 0, &zExecErr); /* On empty table, this should succeed with SQLITE_OK and no error message */ if( rc!=SQLITE_OK && zExecErr ){ fprintf(stderr, "Unexpected error on empty table: %s\n", zExecErr); } TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); TEST_ASSERT_NULL(zExecErr); sqlite3_free(zSql); } void test_sqlite3ErrorIfNotEmpty_nonempty_table_raises_error_with_message(void){ /* Setup schema: table with one row */ exec_or_fail("CREATE TABLE t2(a)"); exec_or_fail("INSERT INTO t2(a) VALUES(123)"); /* Call target wrapper */ Parse s = {0}; init_parse(&s); const char *zErr = "cannot proceed: table has rows"; test_sqlite3ErrorIfNotEmpty(&s, "main", "t2", zErr); cleanup_parse(&s); /* Validate behavior by executing equivalent SQL directly */ char *zSql = sqlite3_mprintf("SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", zErr, "main", "t2"); TEST_ASSERT_NOT_NULL(zSql); char *zExecErr = NULL; int rc = sqlite3_exec(gDb, zSql, 0, 0, &zExecErr); /* Expect an error. Historically, RAISE(ABORT, ...) maps to a constraint error. So just assert that it is not SQLITE_OK and that the message contains our text. */ TEST_ASSERT_NOT_EQUAL(INT, SQLITE_OK, rc); TEST_ASSERT_NOT_NULL(zExecErr); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(zExecErr, zErr), "Error message should contain the provided zErr text"); sqlite3_free(zExecErr); sqlite3_free(zSql); } void test_sqlite3ErrorIfNotEmpty_identifier_quoting_with_special_table_name(void){ /* Create a table name with quotes and a dot to verify proper identifier escaping */ const char *zTab = "weird\"name.dot"; char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\"(a)", zTab); TEST_ASSERT_NOT_NULL(zCreate); exec_or_fail(zCreate); sqlite3_free(zCreate); /* Call target wrapper using an error message with embedded quotes to exercise %Q */ Parse s = {0}; init_parse(&s); const char *zErr = "msg with \"quotes\" and 'single quotes'"; test_sqlite3ErrorIfNotEmpty(&s, "main", zTab, zErr); cleanup_parse(&s); /* Empty table: executing the equivalent SQL should succeed */ char *zSql = sqlite3_mprintf("SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", zErr, "main", zTab); TEST_ASSERT_NOT_NULL(zSql); char *zExecErr = NULL; int rc = sqlite3_exec(gDb, zSql, 0, 0, &zExecErr); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); TEST_ASSERT_NULL(zExecErr); sqlite3_free(zSql); /* Now insert a row and confirm it fails with the message */ char *zInsert = sqlite3_mprintf("INSERT INTO \"%w\"(a) VALUES(1)", zTab); TEST_ASSERT_NOT_NULL(zInsert); exec_or_fail(zInsert); sqlite3_free(zInsert); zSql = sqlite3_mprintf("SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", zErr, "main", zTab); TEST_ASSERT_NOT_NULL(zSql); rc = sqlite3_exec(gDb, zSql, 0, 0, &zExecErr); TEST_ASSERT_NOT_EQUAL(INT, SQLITE_OK, rc); TEST_ASSERT_NOT_NULL(zExecErr); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(zExecErr, zErr), "Error message should contain zErr"); sqlite3_free(zExecErr); sqlite3_free(zSql); } void test_sqlite3ErrorIfNotEmpty_nonexistent_table_sets_parse_error(void){ /* Call target wrapper for a table that does not exist to ensure parse error captured */ Parse s = {0}; init_parse(&s); test_sqlite3ErrorIfNotEmpty(&s, "main", "no_such_table_xyz", "any error"); /* We expect an error recorded by the nested parse */ TEST_ASSERT_TRUE(s.nErr > 0); TEST_ASSERT_NOT_NULL(s.zErrMsg); /* Avoid asserting exact text; just ensure an error was captured */ cleanup_parse(&s); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_sqlite3ErrorIfNotEmpty_empty_table_executes_without_error); RUN_TEST(test_sqlite3ErrorIfNotEmpty_nonempty_table_raises_error_with_message); RUN_TEST(test_sqlite3ErrorIfNotEmpty_identifier_quoting_with_special_table_name); RUN_TEST(test_sqlite3ErrorIfNotEmpty_nonexistent_table_sets_parse_error); return UNITY_END(); }