fix(ai): guard NULL search_str and update tests for new cycling behavior
Some checks failed
CI Code / Check spelling (pull_request) Successful in 21s
CI Code / Check coding style (pull_request) Successful in 37s
CI Code / Linux (debian) (pull_request) Failing after 2m56s
CI Code / Linux (ubuntu) (pull_request) Failing after 3m7s
CI Code / Code Coverage (pull_request) Successful in 2m55s
CI Code / Linux (arch) (pull_request) Failing after 3m45s

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:
2026-05-01 13:08:28 +00:00
parent 4913a3d5a4
commit 00f11eb704
5 changed files with 32 additions and 32 deletions

View File

@@ -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*

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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),