All checks were successful
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 29s
CI Code / Code Coverage (push) Successful in 2m44s
CI Code / Linux (debian) (push) Successful in 4m46s
CI Code / Linux (ubuntu) (push) Successful in 4m59s
CI Code / Linux (arch) (push) Successful in 5m56s
Add an AI client module that integrates with OpenAI-compatible API providers (OpenAI, Perplexity, and custom endpoints) to provide AI-assisted chat within CProof. Users can start sessions with /ai start, send prompts, receive responses in a dedicated AI window, switch between providers and models, and manage API keys — all with tab-completion. Providers are configured via /ai set commands with per-provider API keys, endpoints, default models, and custom settings. Two default providers (openai, perplexity) are seeded on first use. Provider state persists in [ai/<name>] sections of the preferences keyfile with automatic migration from the previous flat-key format. The /ai command integrates into the existing command system with 8 subcommands covering provider management, session lifecycle, model fetching, and conversation clearing. Autocomplete uses the standard flat prefix-matching chain for reliable tab-completion at every nesting level. A new ProfAiWin window type is added to the window system. Architecture: Async design: HTTP requests run on a background thread (pthread) to avoid blocking the ncurses UI loop; results are displayed on the main thread via direct function calls Thread safety: AIProvider and AISession use atomic ref-counting and mutex-protected session state; the request thread snapshots all session data before making the HTTP call Window validation: wins_ai_exists() prevents use-after-free when the user closes the AI window during an in-flight HTTP request (~60s) Privacy: store:false is sent with every request to prevent providers from persisting conversations or using them for training Response size limit: 10MB cap with immediate curl abort via CURL_WRITEFUNC_ERROR to prevent OOM JSON parsing uses unified helpers for both chat responses and error envelopes with consistent escape decoding. The response parser tries Perplexity /v1/responses "text" field first, then falls back to OpenAI "content". Error parsing extracts provider error.message from the standard envelope format. Model parsing handles multiple API response formats (OpenAI list, Perplexity, array) including edge cases. Tests include 470+ lines of unit tests covering provider management, session lifecycle, JSON parsing (multiple formats), autocomplete cycling, and error handling, plus functional tests for /ai command dispatch. A stub_ai.c module isolates unit tests from UI dependencies.
141 lines
6.5 KiB
C
141 lines
6.5 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_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_empty_search_str(void** state);
|
|
void test_ai_providers_find_null_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_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);
|
|
|
|
/* Autocomplete deeper coverage */
|
|
void test_ai_providers_find_after_remove_skips_removed(void** state);
|
|
|
|
/* Reset hook + persistence */
|
|
void test_ai_providers_reset_ac_restarts_cycle(void** state);
|
|
void test_ai_add_provider_persisted_across_init(void** state);
|
|
void test_ai_remove_provider_persisted_across_init(void** state);
|
|
void test_ai_models_persisted_across_init(void** state);
|