AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5#include <stdio.h>6 7/* External wrapper provided in the module for the static function */8void test_jsonPatchFunc(sqlite3_context *ctx, int argc, sqlite3_value **argv);9 10/* We'll expose it to SQL via a tiny shim that calls the wrapper directly */11static void sql_json_mergepatch_text(sqlite3_context *ctx, int argc, sqlite3_value **argv){12 /* Call the provided wrapper directly. User data (sqlite3_user_data(ctx))13 is configured by sqlite3_create_function in setUp(). */14 test_jsonPatchFunc(ctx, argc, argv);15}16 17static sqlite3 *gDb = NULL;18 19/* Helper: execute a scalar SQL and return a heap-allocated copy of result text.20 On success, returns non-NULL and sets *pRc to SQLITE_ROW.21 On error (including no row), returns NULL and *pRc is the SQLite rc (prepare/step). */22static char *execScalarText(sqlite3 *db, const char *sql, int *pRc){23 sqlite3_stmt *pStmt = NULL;24 int rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);25 if( rc!=SQLITE_OK ){26 if( pRc ) *pRc = rc;27 return NULL;28 }29 rc = sqlite3_step(pStmt);30 if( rc==SQLITE_ROW ){31 const unsigned char *z = sqlite3_column_text(pStmt, 0);32 int n = sqlite3_column_bytes(pStmt, 0);33 char *out = (char*)sqlite3_malloc(n+1);34 if( out ){35 memcpy(out, z ? z : (const unsigned char*)"", n);36 out[n] = 0;37 }38 sqlite3_finalize(pStmt);39 if( pRc ) *pRc = SQLITE_ROW;40 return out;41 }else{42 if( pRc ) *pRc = rc;43 sqlite3_finalize(pStmt);44 return NULL;45 }46}47 48/* Helper: expect the SQL to fail at prepare or step time */49static int execExpectError(sqlite3 *db, const char *sql, char **pzErrMsgOut){50 sqlite3_stmt *pStmt = NULL;51 int rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);52 if( rc!=SQLITE_OK ){53 if( pzErrMsgOut ){54 const char *msg = sqlite3_errmsg(db);55 if( msg ){56 *pzErrMsgOut = sqlite3_mprintf("%s", msg);57 }else{58 *pzErrMsgOut = NULL;59 }60 }61 if( pStmt ) sqlite3_finalize(pStmt);62 return rc;63 }64 rc = sqlite3_step(pStmt);65 if( rc==SQLITE_ROW ){66 /* Unexpected success */67 sqlite3_finalize(pStmt);68 if( pzErrMsgOut ) *pzErrMsgOut = NULL;69 return SQLITE_OK;70 }71 /* rc should be SQLITE_ERROR or another error code */72 if( pzErrMsgOut ){73 const char *msg = sqlite3_errmsg(db);74 if( msg ){75 *pzErrMsgOut = sqlite3_mprintf("%s", msg);76 }else{77 *pzErrMsgOut = NULL;78 }79 }80 sqlite3_finalize(pStmt);81 return rc;82}83 84void setUp(void) {85 int rc = sqlite3_open(":memory:", &gDb);86 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "Failed to open in-memory SQLite DB");87 88 /* Register our function that directly calls the target wrapper.89 Set user-data to 0 so jsonPatchFunc returns JSON text, not JSONB blob. */90 rc = sqlite3_create_function(gDb,91 "t_json_mergepatch_text",92 2, /* nargs */93 SQLITE_UTF8,94 (void*)0, /* user-data => flags = 0 */95 sql_json_mergepatch_text,96 0, 0);97 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "Failed to create t_json_mergepatch_text function");98}99 100void tearDown(void) {101 if( gDb ){102 sqlite3_close(gDb);103 gDb = NULL;104 }105}106 107/* If the patch is not an object, it replaces the entire target (lines 02-03) */108void test_jsonPatchFunc_replace_with_non_object_patch(void){109 int rc;110 /* target is an object, patch is a string => result is the patch itself */111 const char *sql =112 "SELECT t_json_mergepatch_text('{\"a\":1}', '\"x\"');";113 char *res = execScalarText(gDb, sql, &rc);114 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);115 TEST_ASSERT_NOT_NULL(res);116 TEST_ASSERT_EQUAL_STRING("\"x\"", res);117 sqlite3_free(res);118}119 120/* If the patch is an object and the target is not an object, target becomes {} before merge */121void test_jsonPatchFunc_patch_on_non_object_target(void){122 int rc;123 const char *sql =124 "SELECT t_json_mergepatch_text('5', '{\"a\":1}');";125 char *res = execScalarText(gDb, sql, &rc);126 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);127 TEST_ASSERT_NOT_NULL(res);128 TEST_ASSERT_EQUAL_STRING("{\"a\":1}", res);129 sqlite3_free(res);130}131 132/* Deleting a property by setting it to null in the patch (lines 09-10) */133void test_jsonPatchFunc_delete_property_with_null(void){134 int rc;135 const char *sql =136 "SELECT t_json_mergepatch_text('{\"a\":1,\"b\":2}', '{\"a\":null}');";137 char *res = execScalarText(gDb, sql, &rc);138 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);139 TEST_ASSERT_NOT_NULL(res);140 TEST_ASSERT_EQUAL_STRING("{\"b\":2}", res);141 sqlite3_free(res);142}143 144/* No-op deletion when key does not exist (lines 09-10) */145void test_jsonPatchFunc_delete_nonexistent_key_noop(void){146 int rc;147 const char *sql =148 "SELECT t_json_mergepatch_text('{}', '{\"z\":null}');";149 char *res = execScalarText(gDb, sql, &rc);150 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);151 TEST_ASSERT_NOT_NULL(res);152 TEST_ASSERT_EQUAL_STRING("{}", res);153 sqlite3_free(res);154}155 156/* Recursive merge into objects (lines 12 and recursion) */157void test_jsonPatchFunc_recursive_merge(void){158 int rc;159 const char *sql =160 "SELECT t_json_mergepatch_text('{\"a\":{\"b\":1,\"c\":2}}', '{\"a\":{\"b\":null,\"d\":3}}');";161 char *res = execScalarText(gDb, sql, &rc);162 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);163 TEST_ASSERT_NOT_NULL(res);164 /* Expect: remove a.b, keep a.c, add a.d; canonical order likely c then d */165 TEST_ASSERT_EQUAL_STRING("{\"a\":{\"c\":2,\"d\":3}}", res);166 sqlite3_free(res);167}168 169/* Add new key where patch value is object: equivalent to MergePatch('{}', value) (lines 16-17) */170void test_jsonPatchFunc_add_object_value_new_key(void){171 int rc;172 const char *sql =173 "SELECT t_json_mergepatch_text('{}', '{\"a\":{\"b\":2}}');";174 char *res = execScalarText(gDb, sql, &rc);175 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);176 TEST_ASSERT_NOT_NULL(res);177 TEST_ASSERT_EQUAL_STRING("{\"a\":{\"b\":2}}", res);178 sqlite3_free(res);179}180 181/* Accept JSONB inputs: feed jsonb() blobs, still return text (user-data flags=0) */182void test_jsonPatchFunc_accepts_jsonb_inputs(void){183 int rc;184 const char *sql =185 "SELECT t_json_mergepatch_text(jsonb('{\"a\":1}'), jsonb('{\"b\":2}'));";186 char *res = execScalarText(gDb, sql, &rc);187 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);188 TEST_ASSERT_NOT_NULL(res);189 TEST_ASSERT_EQUAL_STRING("{\"a\":1,\"b\":2}", res);190 sqlite3_free(res);191}192 193/* Malformed JSON input should raise an error */194void test_jsonPatchFunc_error_on_malformed_json(void){195 char *zErr = NULL;196 /* Pass a malformed JSON string as the patch argument */197 const char *sql =198 "SELECT t_json_mergepatch_text('{\"a\":1}', '{invalid}');";199 int rc = execExpectError(gDb, sql, &zErr);200 TEST_ASSERT_NOT_EQUAL_MESSAGE(SQLITE_ROW, rc, "Malformed JSON unexpectedly returned a row");201 /* We expect an error of some kind: SQLITE_ERROR is most likely */202 TEST_ASSERT_NOT_EQUAL(SQLITE_OK, rc);203 if( zErr ){204 /* Just ensure we got some error message back */205 TEST_ASSERT_TRUE_MESSAGE(strlen(zErr) > 0, "Expected an error message for malformed JSON");206 sqlite3_free(zErr);207 }208}209 210int main(void) {211 UNITY_BEGIN();212 RUN_TEST(test_jsonPatchFunc_replace_with_non_object_patch);213 RUN_TEST(test_jsonPatchFunc_patch_on_non_object_target);214 RUN_TEST(test_jsonPatchFunc_delete_property_with_null);215 RUN_TEST(test_jsonPatchFunc_delete_nonexistent_key_noop);216 RUN_TEST(test_jsonPatchFunc_recursive_merge);217 RUN_TEST(test_jsonPatchFunc_add_object_value_new_key);218 RUN_TEST(test_jsonPatchFunc_accepts_jsonb_inputs);219 RUN_TEST(test_jsonPatchFunc_error_on_malformed_json);220 return UNITY_END();221}