Unit tests (tests/unittests/test_ai_client.c): +50 cases on top of the
existing 50 — total 100. Covers:
- chat response parser (ai_parse_response): OpenAI content + Perplexity
text formats, escape decoding, empty/null/missing inputs, format-string
safety, multiline content
- error envelope parser (ai_parse_error_message): standard envelope,
nested escapes, missing fields, null/empty
- extended JSON escape: \b, \f, \r, all-specials, UTF-8 pass-through
- provider autocomplete cycling with >=2 matches and wrap-around
- session edge cases: NULL args, 100-message order preservation,
set_model(NULL), ref/unref(NULL)
- provider edge cases: get/remove with NULL, double-remove, survival
via session ref after ai_remove_provider
- settings: multi-key independence, missing key, cross-provider
isolation
- model parsing edges: data not array, empty data, "id" outside data,
multiple models
- prefs round-trip: set token -> shutdown -> init -> token reloaded
from disk (uses load_preferences fixture)
Functional tests:
- Tier A (test_ai.c): 15 cases for the /ai command surface that don't
need HTTP. Covers /ai help, providers list, set provider/token,
start with/without key/unknown provider, clear, remove, default
provider/model, switch without window, bad subcommand.
- Tier B (test_ai_http.c + ai_http_stub.{c,h,py}): 5 cases that
exercise the libcurl path against a local Python HTTP stub. The
stub serves canned bodies in five modes (ok, openai, 401, 500,
models) so each chat/models/error path is hit end-to-end without
network.
Infrastructure:
- TEST_GROUPS bumped to 5 in proftest.c; AI tests live in their own
Group 5 because mixing them with stabber-driven tests in Group 4
poisoned stbbr_stop() teardown.
- PROF_FUNC_TEST_AI macro in functionaltests.c registers AI tests
with ai_init_test() which wraps init_prof_test with a prof_connect()
so stabber sees a graceful disconnect at teardown.
- dist_check_DATA ships ai_http_stub.py with the source tarball.
Source-level changes to enable testing:
- src/ai/ai_client.{c,h}: dropped 'static' from _parse_ai_response
(renamed ai_parse_response) and _parse_error_response (renamed
ai_parse_error_message); declared under a "Parsing helpers (exposed
for testing)" section. Same approach as ai_parse_models_from_json.
Results in Docker (cproof-debian image):
- 595/595 unit tests pass
- 20/20 AI functional tests pass (Group 5)
135 lines
6.3 KiB
C
135 lines
6.3 KiB
C
/*
|
|
* test_ai_client.h - AI client unit test declarations
|
|
*/
|
|
|
|
int ai_client_setup(void** state);
|
|
int ai_client_teardown(void** state);
|
|
|
|
void test_ai_client_init(void** state);
|
|
void test_ai_add_provider(void** state);
|
|
void test_ai_remove_provider(void** state);
|
|
void test_ai_list_providers(void** state);
|
|
void test_ai_set_provider_key(void** state);
|
|
void test_ai_get_provider_key(void** state);
|
|
void test_ai_session_create(void** state);
|
|
void test_ai_session_ref_unref(void** state);
|
|
void test_ai_session_add_message(void** state);
|
|
void test_ai_session_clear_history(void** state);
|
|
void test_ai_session_set_model(void** state);
|
|
void test_ai_json_escape(void** state);
|
|
void test_ai_json_escape_null(void** state);
|
|
void test_ai_json_escape_empty(void** state);
|
|
void test_ai_json_escape_special_chars(void** state);
|
|
void test_ai_json_escape_percent_signs(void** state);
|
|
void test_ai_json_escape_backslash_quote(void** state);
|
|
void test_ai_session_api_key_is_copied(void** state);
|
|
void test_ai_add_provider_update_existing(void** state);
|
|
void test_ai_add_provider_null_name_returns_null(void** state);
|
|
void test_ai_add_provider_null_url_returns_null(void** state);
|
|
void test_ai_session_create_null_provider_returns_null(void** state);
|
|
void test_ai_session_create_null_model_returns_null(void** state);
|
|
void test_ai_session_api_key_null_when_no_key_set(void** state);
|
|
/* Provider autocomplete tests */
|
|
void test_ai_providers_find_forward(void** state);
|
|
void test_ai_providers_find_forward_perplexity(void** state);
|
|
void test_ai_providers_find_forward_custom(void** state);
|
|
void test_ai_providers_find_forward_no_match(void** state);
|
|
void test_ai_providers_find_forward_partial_match(void** state);
|
|
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_insensitive(void** state);
|
|
/* Provider default model and settings tests */
|
|
void test_ai_set_provider_default_model(void** state);
|
|
void test_ai_get_provider_default_model(void** state);
|
|
void test_ai_set_provider_setting(void** state);
|
|
void test_ai_get_provider_setting(void** state);
|
|
/* Model caching tests */
|
|
void test_ai_models_are_fresh_initial(void** state);
|
|
/* Model parsing tests */
|
|
void test_ai_parse_models_openai_format(void** state);
|
|
void test_ai_parse_models_perplexity_format(void** state);
|
|
void test_ai_parse_models_array_format(void** state);
|
|
void test_ai_parse_models_empty_json(void** state);
|
|
void test_ai_parse_models_null_handling(void** state);
|
|
void test_ai_parse_models_escaped_quotes(void** state);
|
|
void test_ai_parse_models_with_whitespace(void** state);
|
|
/* AI autocomplete integration tests */
|
|
void test_ai_start_provider_autocomplete_only_on_exact(void** state);
|
|
void test_ai_models_find_null_session(void** state);
|
|
void test_ai_models_find_null_provider(void** state);
|
|
void test_ai_providers_find_cycling(void** state);
|
|
|
|
/* Setup that also initializes prefs for round-trip persistence tests. */
|
|
int ai_client_setup_with_prefs(void** state);
|
|
int ai_client_teardown_with_prefs(void** state);
|
|
|
|
/* Chat response parser tests (ai_parse_response) */
|
|
void test_ai_parse_response_openai_content(void** state);
|
|
void test_ai_parse_response_perplexity_text(void** state);
|
|
void test_ai_parse_response_text_preferred_over_content(void** state);
|
|
void test_ai_parse_response_escaped_quote(void** state);
|
|
void test_ai_parse_response_newline_escape(void** state);
|
|
void test_ai_parse_response_empty_content(void** state);
|
|
void test_ai_parse_response_missing_field(void** state);
|
|
void test_ai_parse_response_null_input(void** state);
|
|
void test_ai_parse_response_empty_input(void** state);
|
|
void test_ai_parse_response_percent_signs_safe(void** state);
|
|
void test_ai_parse_response_braces_in_content(void** state);
|
|
void test_ai_parse_response_multiline_content(void** state);
|
|
|
|
/* Error response parser tests (ai_parse_error_message) */
|
|
void test_ai_parse_error_standard_envelope(void** state);
|
|
void test_ai_parse_error_with_escapes(void** state);
|
|
void test_ai_parse_error_no_error_field(void** state);
|
|
void test_ai_parse_error_no_message_field(void** state);
|
|
void test_ai_parse_error_null_input(void** state);
|
|
void test_ai_parse_error_empty_input(void** state);
|
|
void test_ai_parse_error_tab_escape(void** state);
|
|
void test_ai_parse_error_backslash_escape(void** state);
|
|
|
|
/* Extended JSON escape tests */
|
|
void test_ai_json_escape_backspace(void** state);
|
|
void test_ai_json_escape_formfeed(void** state);
|
|
void test_ai_json_escape_carriage_return(void** state);
|
|
void test_ai_json_escape_all_specials_combined(void** state);
|
|
void test_ai_json_escape_passes_utf8_through(void** state);
|
|
|
|
/* Autocomplete cycling with many providers */
|
|
void test_ai_providers_find_cycles_through_many(void** state);
|
|
void test_ai_providers_find_previous_walks_backwards(void** state);
|
|
void test_ai_providers_find_wraps_around(void** state);
|
|
|
|
/* Session edge cases */
|
|
void test_ai_session_add_message_null_session_no_crash(void** state);
|
|
void test_ai_session_add_message_null_role_no_crash(void** state);
|
|
void test_ai_session_add_message_null_content_no_crash(void** state);
|
|
void test_ai_session_history_preserves_large_order(void** state);
|
|
void test_ai_session_clear_empty_history(void** state);
|
|
void test_ai_session_set_model_null_keeps_old(void** state);
|
|
void test_ai_session_unref_null_no_crash(void** state);
|
|
void test_ai_session_ref_null_returns_null(void** state);
|
|
|
|
/* Provider edge cases */
|
|
void test_ai_get_provider_null_returns_null(void** state);
|
|
void test_ai_remove_provider_null_returns_false(void** state);
|
|
void test_ai_remove_provider_twice_second_fails(void** state);
|
|
void test_ai_provider_survives_via_session_after_removal(void** state);
|
|
|
|
/* Settings edge cases */
|
|
void test_ai_settings_multiple_keys_independent(void** state);
|
|
void test_ai_settings_get_missing_returns_null(void** state);
|
|
void test_ai_settings_isolated_between_providers(void** state);
|
|
|
|
/* Model parsing edge cases */
|
|
void test_ai_parse_models_data_not_array(void** state);
|
|
void test_ai_parse_models_empty_data_array(void** state);
|
|
void test_ai_parse_models_id_outside_data_ignored(void** state);
|
|
void test_ai_parse_models_multiple_models(void** state);
|
|
|
|
/* Prefs round-trip tests */
|
|
void test_ai_prefs_round_trip_api_key(void** state);
|
|
void test_ai_prefs_round_trip_remove_key(void** state);
|
|
void test_ai_prefs_multiple_providers_persist(void** state);
|