@@ -623,4 +623,236 @@ def test_fingerprint_hash_compatibility_with_sync(tmp_path):
623623 assert result .command == "generate"
624624
625625 # Verify pdd_version is set
626- assert result .pdd_version is not None , "pdd_version should be set"
626+ assert result .pdd_version is not None , "pdd_version should be set"
627+
628+
629+ # --------------------------------------------------------------------------------
630+ # ISSUE #203: test_prompt_hash auto-management in save_fingerprint
631+ # --------------------------------------------------------------------------------
632+
633+ class TestIssue203SaveFingerprintTestPromptHash :
634+ """Test that save_fingerprint automatically manages test_prompt_hash based on operation type."""
635+
636+ def test_generate_operation_sets_test_prompt_hash_to_none (self , tmp_path ):
637+ """
638+ Issue #203: When operation='generate', test_prompt_hash should be None
639+ because code was regenerated and tests are now stale.
640+ """
641+ from pdd .operation_log import save_fingerprint
642+ from pdd .sync_determine_operation import read_fingerprint
643+
644+ basename = "gen_test"
645+ language = "python"
646+
647+ meta_dir = tmp_path / ".pdd" / "meta"
648+ meta_dir .mkdir (parents = True )
649+
650+ # Create existing fingerprint with test_prompt_hash set
651+ existing_fp = meta_dir / f"{ basename } _{ language } .json"
652+ existing_fp .write_text (json .dumps ({
653+ "pdd_version" : "0.0.1" ,
654+ "timestamp" : "2024-01-01T00:00:00" ,
655+ "command" : "test" ,
656+ "prompt_hash" : "old_prompt_hash" ,
657+ "code_hash" : None ,
658+ "example_hash" : None ,
659+ "test_hash" : None ,
660+ "test_files" : None ,
661+ "test_prompt_hash" : "existing_test_prompt_hash"
662+ }))
663+
664+ with patch ("pdd.operation_log.META_DIR" , str (meta_dir )), \
665+ patch ("pdd.sync_determine_operation.get_meta_dir" , return_value = meta_dir ):
666+
667+ # Call save_fingerprint with operation='generate' (no explicit test_prompt_hash)
668+ save_fingerprint (
669+ basename = basename ,
670+ language = language ,
671+ operation = "generate" ,
672+ paths = {},
673+ cost = 0.1 ,
674+ model = "test"
675+ )
676+
677+ # Read back and verify test_prompt_hash is None
678+ result = read_fingerprint (basename , language )
679+ assert result is not None
680+ assert result .test_prompt_hash is None , (
681+ "generate operation should set test_prompt_hash to None (tests now stale)"
682+ )
683+
684+ def test_test_operation_sets_test_prompt_hash_to_current (self , tmp_path ):
685+ """
686+ Issue #203: When operation='test', test_prompt_hash should be set to
687+ the current prompt hash (tests regenerated, linked to current prompt).
688+ """
689+ from pdd .operation_log import save_fingerprint
690+ from pdd .sync_determine_operation import read_fingerprint
691+
692+ basename = "test_op_test"
693+ language = "python"
694+
695+ meta_dir = tmp_path / ".pdd" / "meta"
696+ prompts_dir = tmp_path / "prompts"
697+ meta_dir .mkdir (parents = True )
698+ prompts_dir .mkdir (parents = True )
699+
700+ # Create a prompt file with known content
701+ prompt_file = prompts_dir / f"{ basename } _{ language } .prompt"
702+ prompt_file .write_text ("% Test prompt content\n " )
703+
704+ paths = {"prompt" : prompt_file }
705+
706+ with patch ("pdd.operation_log.META_DIR" , str (meta_dir )), \
707+ patch ("pdd.sync_determine_operation.get_meta_dir" , return_value = meta_dir ):
708+
709+ # Call save_fingerprint with operation='test'
710+ save_fingerprint (
711+ basename = basename ,
712+ language = language ,
713+ operation = "test" ,
714+ paths = paths ,
715+ cost = 0.1 ,
716+ model = "test"
717+ )
718+
719+ # Read back and verify test_prompt_hash equals prompt_hash
720+ result = read_fingerprint (basename , language )
721+ assert result is not None
722+ assert result .prompt_hash is not None , "prompt_hash should be calculated"
723+ assert result .test_prompt_hash == result .prompt_hash , (
724+ "test operation should set test_prompt_hash to current prompt_hash"
725+ )
726+
727+ def test_example_operation_preserves_test_prompt_hash (self , tmp_path ):
728+ """
729+ Issue #203: When operation is not 'generate' or 'test', the existing
730+ test_prompt_hash should be preserved.
731+ """
732+ from pdd .operation_log import save_fingerprint
733+ from pdd .sync_determine_operation import read_fingerprint
734+
735+ basename = "example_test"
736+ language = "python"
737+
738+ meta_dir = tmp_path / ".pdd" / "meta"
739+ meta_dir .mkdir (parents = True )
740+
741+ existing_test_prompt_hash = "preserved_hash_value"
742+
743+ # Create existing fingerprint with test_prompt_hash set
744+ existing_fp = meta_dir / f"{ basename } _{ language } .json"
745+ existing_fp .write_text (json .dumps ({
746+ "pdd_version" : "0.0.1" ,
747+ "timestamp" : "2024-01-01T00:00:00" ,
748+ "command" : "test" ,
749+ "prompt_hash" : "some_hash" ,
750+ "code_hash" : None ,
751+ "example_hash" : None ,
752+ "test_hash" : None ,
753+ "test_files" : None ,
754+ "test_prompt_hash" : existing_test_prompt_hash
755+ }))
756+
757+ with patch ("pdd.operation_log.META_DIR" , str (meta_dir )), \
758+ patch ("pdd.sync_determine_operation.get_meta_dir" , return_value = meta_dir ):
759+
760+ # Call save_fingerprint with operation='example'
761+ save_fingerprint (
762+ basename = basename ,
763+ language = language ,
764+ operation = "example" ,
765+ paths = {},
766+ cost = 0.1 ,
767+ model = "test"
768+ )
769+
770+ # Read back and verify test_prompt_hash is preserved
771+ result = read_fingerprint (basename , language )
772+ assert result is not None
773+ assert result .test_prompt_hash == existing_test_prompt_hash , (
774+ "example operation should preserve existing test_prompt_hash"
775+ )
776+
777+ def test_fix_operation_preserves_test_prompt_hash (self , tmp_path ):
778+ """
779+ Issue #203: Fix operation should also preserve existing test_prompt_hash.
780+ """
781+ from pdd .operation_log import save_fingerprint
782+ from pdd .sync_determine_operation import read_fingerprint
783+
784+ basename = "fix_test"
785+ language = "python"
786+
787+ meta_dir = tmp_path / ".pdd" / "meta"
788+ meta_dir .mkdir (parents = True )
789+
790+ existing_test_prompt_hash = "fix_preserved_hash"
791+
792+ # Create existing fingerprint
793+ existing_fp = meta_dir / f"{ basename } _{ language } .json"
794+ existing_fp .write_text (json .dumps ({
795+ "pdd_version" : "0.0.1" ,
796+ "timestamp" : "2024-01-01T00:00:00" ,
797+ "command" : "test" ,
798+ "prompt_hash" : "some_hash" ,
799+ "code_hash" : None ,
800+ "example_hash" : None ,
801+ "test_hash" : None ,
802+ "test_files" : None ,
803+ "test_prompt_hash" : existing_test_prompt_hash
804+ }))
805+
806+ with patch ("pdd.operation_log.META_DIR" , str (meta_dir )), \
807+ patch ("pdd.sync_determine_operation.get_meta_dir" , return_value = meta_dir ):
808+
809+ save_fingerprint (
810+ basename = basename ,
811+ language = language ,
812+ operation = "fix" ,
813+ paths = {},
814+ cost = 0.1 ,
815+ model = "test"
816+ )
817+
818+ result = read_fingerprint (basename , language )
819+ assert result is not None
820+ assert result .test_prompt_hash == existing_test_prompt_hash , (
821+ "fix operation should preserve existing test_prompt_hash"
822+ )
823+
824+ def test_explicit_test_prompt_hash_overrides_auto_logic (self , tmp_path ):
825+ """
826+ Issue #203: When test_prompt_hash is explicitly passed, it should override
827+ the automatic logic.
828+ """
829+ from pdd .operation_log import save_fingerprint
830+ from pdd .sync_determine_operation import read_fingerprint
831+
832+ basename = "explicit_test"
833+ language = "python"
834+
835+ meta_dir = tmp_path / ".pdd" / "meta"
836+ meta_dir .mkdir (parents = True )
837+
838+ explicit_hash = "explicitly_passed_hash"
839+
840+ with patch ("pdd.operation_log.META_DIR" , str (meta_dir )), \
841+ patch ("pdd.sync_determine_operation.get_meta_dir" , return_value = meta_dir ):
842+
843+ # Even for 'generate' operation, explicit test_prompt_hash should be used
844+ save_fingerprint (
845+ basename = basename ,
846+ language = language ,
847+ operation = "generate" ,
848+ paths = {},
849+ cost = 0.1 ,
850+ model = "test" ,
851+ test_prompt_hash = explicit_hash
852+ )
853+
854+ result = read_fingerprint (basename , language )
855+ assert result is not None
856+ assert result .test_prompt_hash == explicit_hash , (
857+ "Explicit test_prompt_hash should override automatic logic"
858+ )
0 commit comments