@@ -51,6 +51,50 @@ fn normalize_provider_form(
5151 Ok ( ( ) )
5252}
5353
54+ fn map_provider_lookup_error ( e : & str ) -> String {
55+ if e. contains ( "[GET_AI_PROVIDER_ERROR]" ) {
56+ "Selected AI provider does not exist" . to_string ( )
57+ } else {
58+ e. to_string ( )
59+ }
60+ }
61+
62+ fn map_default_provider_error ( e : & str ) -> String {
63+ if e. contains ( "[NO_ENABLED_AI_PROVIDER]" ) {
64+ "No enabled AI provider is configured. Please enable one in AI Provider settings."
65+ . to_string ( )
66+ } else {
67+ e. to_string ( )
68+ }
69+ }
70+
71+ fn ensure_provider_enabled ( enabled : bool ) -> Result < ( ) , String > {
72+ if enabled {
73+ Ok ( ( ) )
74+ } else {
75+ Err ( "Selected AI provider is disabled" . to_string ( ) )
76+ }
77+ }
78+
79+ fn validate_conversation_requirement (
80+ conversation_id : Option < i64 > ,
81+ create_if_missing : bool ,
82+ ) -> Result < ( ) , String > {
83+ if conversation_id. is_none ( ) && !create_if_missing {
84+ Err ( "conversationId is required" . to_string ( ) )
85+ } else {
86+ Ok ( ( ) )
87+ }
88+ }
89+
90+ fn map_history_load_error ( conversation_id : i64 , e : & str ) -> String {
91+ eprintln ! (
92+ "[AI_HISTORY_LOAD_ERROR] Failed to load messages for conversation {}: {}" ,
93+ conversation_id, e
94+ ) ;
95+ "Failed to load conversation history" . to_string ( )
96+ }
97+
5498async fn get_db ( state : & State < ' _ , AppState > ) -> Result < Arc < crate :: db:: local:: LocalDb > , String > {
5599 let local_db = {
56100 let lock = state. local_db . lock ( ) . await ;
@@ -170,11 +214,7 @@ async fn run_chat(
170214 match db. get_ai_provider_by_id ( provider_id) . await {
171215 Ok ( provider) => provider,
172216 Err ( e) => {
173- let msg = if e. contains ( "[GET_AI_PROVIDER_ERROR]" ) {
174- "Selected AI provider does not exist" . to_string ( )
175- } else {
176- e
177- } ;
217+ let msg = map_provider_lookup_error ( & e) ;
178218 emit_ai_error (
179219 & app,
180220 request. request_id ,
@@ -188,11 +228,7 @@ async fn run_chat(
188228 match db. get_default_ai_provider ( ) . await {
189229 Ok ( provider) => provider,
190230 Err ( e) => {
191- let msg = if e. contains ( "[NO_ENABLED_AI_PROVIDER]" ) {
192- "No enabled AI provider is configured. Please enable one in AI Provider settings." . to_string ( )
193- } else {
194- e
195- } ;
231+ let msg = map_default_provider_error ( & e) ;
196232 emit_ai_error (
197233 & app,
198234 request. request_id ,
@@ -204,8 +240,7 @@ async fn run_chat(
204240 }
205241 } ;
206242
207- if !provider_record. enabled {
208- let msg = "Selected AI provider is disabled" . to_string ( ) ;
243+ if let Err ( msg) = ensure_provider_enabled ( provider_record. enabled ) {
209244 emit_ai_error (
210245 & app,
211246 request. request_id ,
@@ -215,6 +250,8 @@ async fn run_chat(
215250 return Err ( msg) ;
216251 }
217252
253+ validate_conversation_requirement ( request. conversation_id , create_if_missing) ?;
254+
218255 let api_key = db
219256 . decrypt_ai_api_key ( & provider_record. api_key )
220257 . map_err ( |_| {
@@ -242,7 +279,7 @@ async fn run_chat(
242279 )
243280 . await ?
244281 }
245- None => return Err ( "conversationId is required" . to_string ( ) ) ,
282+ None => unreachable ! ( "conversation requirement should be validated before this branch" ) ,
246283 } ;
247284
248285 let user_message = db
@@ -313,11 +350,7 @@ async fn run_chat(
313350 let mut existing = match db. list_ai_messages ( conversation. id ) . await {
314351 Ok ( messages) => messages,
315352 Err ( e) => {
316- eprintln ! (
317- "[AI_HISTORY_LOAD_ERROR] Failed to load messages for conversation {}: {}" ,
318- conversation. id, e
319- ) ;
320- let client_error = "Failed to load conversation history" . to_string ( ) ;
353+ let client_error = map_history_load_error ( conversation. id , & e) ;
321354 emit_ai_error (
322355 & app,
323356 request. request_id . clone ( ) ,
@@ -439,3 +472,83 @@ pub async fn ai_delete_conversation(
439472 let db = get_db ( & state) . await ?;
440473 db. delete_ai_conversation ( conversation_id) . await
441474}
475+
476+ #[ cfg( test) ]
477+ mod tests {
478+ use super :: {
479+ ensure_provider_enabled, map_default_provider_error, map_history_load_error,
480+ map_provider_lookup_error, normalize_provider_type, validate_conversation_requirement,
481+ } ;
482+
483+ #[ test]
484+ fn normalize_provider_type_rejects_empty_value ( ) {
485+ assert_eq ! (
486+ normalize_provider_type( " " ) . unwrap_err( ) ,
487+ "providerType is required"
488+ ) ;
489+ }
490+
491+ #[ test]
492+ fn normalize_provider_type_maps_openai_compat_to_openai ( ) {
493+ assert_eq ! (
494+ normalize_provider_type( "OpenAI_Compat" ) . unwrap( ) ,
495+ "openai" . to_string( )
496+ ) ;
497+ }
498+
499+ #[ test]
500+ fn normalize_provider_type_rejects_invalid_chars ( ) {
501+ assert_eq ! (
502+ normalize_provider_type( "bad type!" ) . unwrap_err( ) ,
503+ "providerType has invalid format"
504+ ) ;
505+ }
506+
507+ #[ test]
508+ fn normalize_provider_type_accepts_supported_chars ( ) {
509+ assert_eq ! (
510+ normalize_provider_type( "x.y-z_1" ) . unwrap( ) ,
511+ "x.y-z_1" . to_string( )
512+ ) ;
513+ }
514+
515+ #[ test]
516+ fn provider_lookup_error_maps_not_found_to_user_friendly_message ( ) {
517+ assert_eq ! (
518+ map_provider_lookup_error( "[GET_AI_PROVIDER_ERROR] row not found" ) ,
519+ "Selected AI provider does not exist"
520+ ) ;
521+ }
522+
523+ #[ test]
524+ fn default_provider_error_maps_no_enabled_provider_to_user_friendly_message ( ) {
525+ assert_eq ! (
526+ map_default_provider_error( "[NO_ENABLED_AI_PROVIDER] nothing configured" ) ,
527+ "No enabled AI provider is configured. Please enable one in AI Provider settings."
528+ ) ;
529+ }
530+
531+ #[ test]
532+ fn ensure_provider_enabled_rejects_disabled_provider ( ) {
533+ assert_eq ! (
534+ ensure_provider_enabled( false ) . unwrap_err( ) ,
535+ "Selected AI provider is disabled"
536+ ) ;
537+ }
538+
539+ #[ test]
540+ fn continue_requires_conversation_id ( ) {
541+ assert_eq ! (
542+ validate_conversation_requirement( None , false ) . unwrap_err( ) ,
543+ "conversationId is required"
544+ ) ;
545+ }
546+
547+ #[ test]
548+ fn history_load_error_maps_to_client_message ( ) {
549+ assert_eq ! (
550+ map_history_load_error( 42 , "[DB_ERROR] broken" ) ,
551+ "Failed to load conversation history"
552+ ) ;
553+ }
554+ }
0 commit comments