@@ -403,3 +403,202 @@ def test_likert_scale_missing_score_value_key_rejected(tmp_path: Path):
403403 chat_target = chat_target ,
404404 likert_scale = LikertScalePaths .CYBER_SCALE ,
405405 )
406+
407+
408+ # ---------------------------------------------------------------------------
409+ # custom_likert_path and custom_system_prompt_path tests
410+ # ---------------------------------------------------------------------------
411+
412+
413+ def _make_custom_system_prompt_yaml (tmp_path : Path , * , include_all_params : bool = True ) -> Path :
414+ """Create a custom system prompt YAML file for testing."""
415+ params = ["category" , "likert_scale" , "min_scale_value" , "max_scale_value" ] if include_all_params else ["category" ]
416+ prompt_data = {
417+ "name" : "custom test prompt" ,
418+ "description" : "test" ,
419+ "parameters" : params ,
420+ "data_type" : "text" ,
421+ "value" : "Custom prompt for {{category}} with scale {{likert_scale}} "
422+ "from {{min_scale_value}} to {{max_scale_value}}."
423+ if include_all_params
424+ else "Only {{category}}." ,
425+ }
426+ yaml_file = tmp_path / "custom_system_prompt.yaml"
427+ yaml_file .write_text (yaml .safe_dump (prompt_data ), encoding = "utf-8" )
428+ return yaml_file
429+
430+
431+ def test_custom_likert_path_creates_scorer (tmp_path : Path ):
432+ """Verify that passing custom_likert_path (instead of a LikertScalePaths enum) works."""
433+ memory = MagicMock (MemoryInterface )
434+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
435+ chat_target = MagicMock ()
436+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
437+
438+ custom_path = _make_custom_scale_yaml (tmp_path , category = "custom_cat" , min_val = 0 , max_val = 3 )
439+ scorer = SelfAskLikertScorer (chat_target = chat_target , custom_likert_path = custom_path )
440+
441+ assert scorer ._min_scale_value == 0
442+ assert scorer ._max_scale_value == 3
443+ assert scorer ._score_category == "custom_cat"
444+
445+
446+ def test_custom_likert_path_file_not_found ():
447+ """Verify that a non-existent custom_likert_path raises FileNotFoundError."""
448+ memory = MagicMock (MemoryInterface )
449+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
450+ chat_target = MagicMock ()
451+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
452+
453+ with pytest .raises (FileNotFoundError , match = "Custom Likert scale file not found" ):
454+ SelfAskLikertScorer (chat_target = chat_target , custom_likert_path = Path ("/does/not/exist.yaml" ))
455+
456+
457+ def test_custom_likert_path_non_yaml_rejected (tmp_path : Path ):
458+ """Verify that a non-YAML custom_likert_path raises ValueError."""
459+ bad_file = tmp_path / "scale.txt"
460+ bad_file .write_text ("not yaml" , encoding = "utf-8" )
461+
462+ memory = MagicMock (MemoryInterface )
463+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
464+ chat_target = MagicMock ()
465+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
466+
467+ with pytest .raises (ValueError , match = "must be a YAML file" ):
468+ SelfAskLikertScorer (chat_target = chat_target , custom_likert_path = bad_file )
469+
470+
471+ def test_custom_system_prompt_non_yaml_rejected (tmp_path : Path ):
472+ """Verify that a non-YAML custom_system_prompt_path raises ValueError."""
473+ bad_file = tmp_path / "prompt.txt"
474+ bad_file .write_text ("not yaml" , encoding = "utf-8" )
475+
476+ memory = MagicMock (MemoryInterface )
477+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
478+ chat_target = MagicMock ()
479+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
480+
481+ with pytest .raises (ValueError , match = "must be a YAML file" ):
482+ SelfAskLikertScorer (
483+ chat_target = chat_target ,
484+ likert_scale = LikertScalePaths .CYBER_SCALE ,
485+ custom_system_prompt_path = bad_file ,
486+ )
487+
488+
489+ def test_custom_system_prompt_path_used_in_system_prompt (tmp_path : Path ):
490+ """Verify that a custom system prompt template is rendered instead of the default."""
491+ memory = MagicMock (MemoryInterface )
492+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
493+ chat_target = MagicMock ()
494+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
495+
496+ custom_prompt_path = _make_custom_system_prompt_yaml (tmp_path )
497+ custom_likert_path = _make_custom_scale_yaml (tmp_path , category = "test_cat" , min_val = 1 , max_val = 5 )
498+
499+ scorer = SelfAskLikertScorer (
500+ chat_target = chat_target ,
501+ custom_likert_path = custom_likert_path ,
502+ custom_system_prompt_path = custom_prompt_path ,
503+ )
504+
505+ # The system prompt should come from the custom template, not the default one
506+ assert "Custom prompt for test_cat" in scorer ._system_prompt
507+ assert "from 1 to 5" in scorer ._system_prompt
508+
509+
510+ def test_custom_system_prompt_missing_params_rejected (tmp_path : Path ):
511+ """Verify that a custom system prompt missing required parameters raises ValueError."""
512+ memory = MagicMock (MemoryInterface )
513+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
514+ chat_target = MagicMock ()
515+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
516+
517+ bad_prompt_path = _make_custom_system_prompt_yaml (tmp_path , include_all_params = False )
518+ custom_likert_path = _make_custom_scale_yaml (tmp_path )
519+
520+ with pytest .raises (ValueError , match = "Custom system prompt YAML must define parameters" ):
521+ SelfAskLikertScorer (
522+ chat_target = chat_target ,
523+ custom_likert_path = custom_likert_path ,
524+ custom_system_prompt_path = bad_prompt_path ,
525+ )
526+
527+
528+ def test_both_likert_scale_and_custom_path_raises ():
529+ """Verify that providing both likert_scale and custom_likert_path raises ValueError."""
530+ memory = MagicMock (MemoryInterface )
531+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
532+ chat_target = MagicMock ()
533+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
534+
535+ with pytest .raises (ValueError , match = "Only one of" ):
536+ SelfAskLikertScorer (
537+ chat_target = chat_target ,
538+ likert_scale = LikertScalePaths .CYBER_SCALE ,
539+ custom_likert_path = Path ("dummy.yaml" ),
540+ )
541+
542+
543+ def test_neither_likert_scale_nor_custom_path_raises ():
544+ """Verify that providing neither likert_scale nor custom_likert_path raises ValueError."""
545+ memory = MagicMock (MemoryInterface )
546+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
547+ chat_target = MagicMock ()
548+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
549+
550+ with pytest .raises (ValueError , match = "One of" ):
551+ SelfAskLikertScorer (chat_target = chat_target )
552+
553+
554+ def test_custom_system_prompt_file_not_found ():
555+ """Verify that a non-existent custom_system_prompt_path raises FileNotFoundError."""
556+ memory = MagicMock (MemoryInterface )
557+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
558+ chat_target = MagicMock ()
559+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
560+
561+ with pytest .raises (FileNotFoundError , match = "Custom system prompt file not found" ):
562+ SelfAskLikertScorer (
563+ chat_target = chat_target ,
564+ likert_scale = LikertScalePaths .CYBER_SCALE ,
565+ custom_system_prompt_path = Path ("/does/not/exist.yaml" ),
566+ )
567+
568+
569+ def test_custom_likert_yaml_not_a_dict_rejected (tmp_path : Path ):
570+ """Verify that a YAML file whose top-level structure is not a dict raises ValueError."""
571+ yaml_file = tmp_path / "bad_structure.yaml"
572+ yaml_file .write_text ("- item1\n - item2\n " , encoding = "utf-8" )
573+
574+ memory = MagicMock (MemoryInterface )
575+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
576+ chat_target = MagicMock ()
577+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
578+
579+ with pytest .raises (ValueError , match = "must contain a YAML mapping/dictionary" ):
580+ SelfAskLikertScorer (chat_target = chat_target , custom_likert_path = yaml_file )
581+
582+
583+ def test_likert_scale_single_unique_value_rejected (tmp_path : Path ):
584+ """Verify that a scale with only one distinct score value raises ValueError."""
585+ yaml_file = tmp_path / "single_value.yaml"
586+ yaml_file .write_text (
587+ yaml .safe_dump (
588+ {
589+ "category" : "test_harm" ,
590+ "scale_descriptions" : [
591+ {"score_value" : "3" , "description" : "Only level" },
592+ ],
593+ }
594+ ),
595+ encoding = "utf-8" ,
596+ )
597+
598+ memory = MagicMock (MemoryInterface )
599+ with patch .object (CentralMemory , "get_memory_instance" , return_value = memory ):
600+ chat_target = MagicMock ()
601+ chat_target .get_identifier .return_value = get_mock_target_identifier ("MockChatTarget" )
602+
603+ with pytest .raises (ValueError , match = "at least two distinct score values" ):
604+ SelfAskLikertScorer (chat_target = chat_target , custom_likert_path = yaml_file )
0 commit comments