mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-07-19 06:16:21 +00:00
fix(ai): guard NULL search_str and update tests for new cycling behavior
Update ai_providers_find() to handle NULL search_str safely by treating it as an empty string, preventing a segfault in strdup() within autocomplete_complete(). Rename test_ai_providers_find_case_sensitive to test_ai_providers_find_case_insensitive to reflect the new case-insensitive matching contract. Update all affected tests to expect cycling behavior (NULL/empty returns first provider) and use auto_gchar for automatic memory management. Use `gchar` instead of `char` in ai_providers_find for consistency
This commit is contained in:
@@ -360,7 +360,7 @@ ai_list_providers(void)
|
||||
/* Stateful provider name finder with case-sensitive matching.
|
||||
* Maintains position in sorted list for deterministic tab-completion cycling.
|
||||
* The autocomplete is kept in sync via ai_add_provider/ai_remove_provider. */
|
||||
char*
|
||||
gchar*
|
||||
ai_providers_find(const char* const search_str, gboolean previous, void* context)
|
||||
{
|
||||
/* Initialize autocomplete on first use */
|
||||
@@ -368,8 +368,11 @@ ai_providers_find(const char* const search_str, gboolean previous, void* context
|
||||
providers_ac = autocomplete_new();
|
||||
}
|
||||
|
||||
/* NULL search_str is treated as empty string for cycling */
|
||||
const char* effective_search = (search_str != NULL) ? search_str : "";
|
||||
|
||||
/* Use stateful autocomplete */
|
||||
return autocomplete_complete(providers_ac, search_str, FALSE, previous);
|
||||
return autocomplete_complete(providers_ac, effective_search, FALSE, previous);
|
||||
}
|
||||
|
||||
gchar*
|
||||
|
||||
@@ -109,7 +109,7 @@ GList* ai_list_providers(void);
|
||||
* @param context Unused
|
||||
* @return Provider name, or NULL if not found
|
||||
*/
|
||||
char* ai_providers_find(const char* const search_str, gboolean previous, void* context);
|
||||
gchar* ai_providers_find(const char* const search_str, gboolean previous, void* context);
|
||||
|
||||
/**
|
||||
* Get the API key for a provider.
|
||||
|
||||
@@ -356,20 +356,18 @@ void
|
||||
test_ai_providers_find_forward(void** state)
|
||||
{
|
||||
/* Test forward iteration - should return first match */
|
||||
char* result = ai_providers_find("o", FALSE, NULL);
|
||||
auto_gchar gchar* result = ai_providers_find("o", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_forward_perplexity(void** state)
|
||||
{
|
||||
/* Test forward iteration for perplexity */
|
||||
char* result = ai_providers_find("p", FALSE, NULL);
|
||||
auto_gchar gchar* result = ai_providers_find("p", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("perplexity", result);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -378,17 +376,16 @@ test_ai_providers_find_forward_custom(void** state)
|
||||
/* Add a custom provider and test */
|
||||
ai_add_provider("custom", "https://custom.api.com/v1", NULL);
|
||||
|
||||
char* result = ai_providers_find("c", FALSE, NULL);
|
||||
auto_gchar gchar* result = ai_providers_find("c", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("custom", result);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_forward_no_match(void** state)
|
||||
{
|
||||
/* Test no match */
|
||||
char* result = ai_providers_find("z", FALSE, NULL);
|
||||
auto_gchar gchar* result = ai_providers_find("z", FALSE, NULL);
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
@@ -396,26 +393,23 @@ void
|
||||
test_ai_providers_find_forward_partial_match(void** state)
|
||||
{
|
||||
/* Test partial match - should return providers starting with "ope" */
|
||||
char* result = ai_providers_find("ope", FALSE, NULL);
|
||||
auto_gchar gchar* result = ai_providers_find("ope", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_next(void** state)
|
||||
{
|
||||
/* Test that stateless implementation returns same result each call */
|
||||
char* result1 = ai_providers_find("o", FALSE, NULL);
|
||||
auto_gchar gchar* result1 = ai_providers_find("o", FALSE, NULL);
|
||||
assert_non_null(result1);
|
||||
assert_string_equal("openai", result1);
|
||||
g_free(result1);
|
||||
|
||||
/* Second call with same params returns same result (stateless) */
|
||||
char* result2 = ai_providers_find("o", FALSE, NULL);
|
||||
auto_gchar gchar* result2 = ai_providers_find("o", FALSE, NULL);
|
||||
assert_non_null(result2);
|
||||
assert_string_equal("openai", result2);
|
||||
g_free(result2);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -423,44 +417,47 @@ test_ai_providers_find_previous(void** state)
|
||||
{
|
||||
/* Test that previous=TRUE returns last match in list */
|
||||
/* With only "openai" starting with "o", both FALSE and TRUE return same result */
|
||||
char* result1 = ai_providers_find("o", FALSE, NULL);
|
||||
auto_gchar gchar* result1 = ai_providers_find("o", FALSE, NULL);
|
||||
assert_non_null(result1);
|
||||
assert_string_equal("openai", result1);
|
||||
g_free(result1);
|
||||
|
||||
/* previous=TRUE also returns "openai" (only one match, so first==last) */
|
||||
char* result2 = ai_providers_find("o", TRUE, NULL);
|
||||
auto_gchar gchar* result2 = ai_providers_find("o", TRUE, NULL);
|
||||
assert_non_null(result2);
|
||||
assert_string_equal("openai", result2);
|
||||
g_free(result2);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_null_search_str(void** state)
|
||||
{
|
||||
char* result = ai_providers_find(NULL, FALSE, NULL);
|
||||
assert_null(result);
|
||||
/* NULL search_str triggers cycling: returns first provider in list */
|
||||
auto_gchar gchar* result = ai_providers_find(NULL, FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_empty_search_str(void** state)
|
||||
{
|
||||
char* result = ai_providers_find("", FALSE, NULL);
|
||||
assert_null(result);
|
||||
/* Empty search_str triggers cycling: returns first provider in list */
|
||||
auto_gchar gchar* result = ai_providers_find("", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_case_sensitive(void** state)
|
||||
test_ai_providers_find_case_insensitive(void** state)
|
||||
{
|
||||
/* Test that matching is case-sensitive */
|
||||
char* result = ai_providers_find("OPENAI", FALSE, NULL);
|
||||
assert_null(result);
|
||||
/* Test that matching is case-insensitive (via g_ascii_strdown) */
|
||||
auto_gchar gchar* result = ai_providers_find("OPENAI", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
|
||||
result = ai_providers_find("OpenAI", FALSE, NULL);
|
||||
assert_null(result);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
|
||||
result = ai_providers_find("openai", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ void test_ai_providers_find_next(void** state);
|
||||
void test_ai_providers_find_previous(void** state);
|
||||
void test_ai_providers_find_null_search_str(void** state);
|
||||
void test_ai_providers_find_empty_search_str(void** state);
|
||||
void test_ai_providers_find_case_sensitive(void** state);
|
||||
void test_ai_providers_find_case_insensitive(void** state);
|
||||
|
||||
@@ -687,7 +687,7 @@ main(int argc, char* argv[])
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_previous, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_null_search_str, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_empty_search_str, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_case_sensitive, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_case_insensitive, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test(test_ai_json_escape),
|
||||
cmocka_unit_test(test_ai_json_escape_null),
|
||||
cmocka_unit_test(test_ai_json_escape_empty),
|
||||
|
||||
Reference in New Issue
Block a user