@@ -560,3 +560,370 @@ def test_file_based_cache_read_data_json_invalid_encrypted_format(
560560    # Test reading invalid encrypted format 
561561    result  =  file_based_cache ._read_data_json (test_file_path , encrypter_with_key )
562562    assert  result  ==  {}
563+ 
564+ 
565+ def  test_file_based_cache_delete_method (file_based_cache , encrypter_with_key ):
566+     """Test FileBasedCache delete method removes data from both memory and file.""" 
567+     # Create test data 
568+     sample_key  =  SecureCacheKey (["delete" , "test" ], "test_secret" )
569+     sample_data  =  ConnectionInfo (id = "test_delete_connection" , token = "test_token" )
570+ 
571+     # Set data in cache (both memory and file) 
572+     file_based_cache .set (sample_key , sample_data )
573+ 
574+     # Verify data exists in memory cache 
575+     memory_result  =  file_based_cache .memory_cache .get (sample_key )
576+     assert  memory_result  is  not   None 
577+     assert  memory_result .id  ==  "test_delete_connection" 
578+ 
579+     # Verify file exists on disk 
580+     file_path  =  file_based_cache ._get_file_path (sample_key )
581+     assert  os .path .exists (file_path )
582+ 
583+     # Delete the data 
584+     file_based_cache .delete (sample_key )
585+ 
586+     # Verify data is removed from memory cache 
587+     memory_result_after_delete  =  file_based_cache .memory_cache .get (sample_key )
588+     assert  memory_result_after_delete  is  None 
589+ 
590+     # Verify file is removed from disk 
591+     assert  not  os .path .exists (file_path )
592+ 
593+     # Verify get returns None 
594+     cache_result  =  file_based_cache .get (sample_key )
595+     assert  cache_result  is  None 
596+ 
597+ 
598+ @mark .nofakefs  
599+ def  test_file_based_cache_delete_method_file_removal_failure (
600+     file_based_cache , encrypter_with_key 
601+ ):
602+     """Test FileBasedCache delete method handles file removal failures gracefully.""" 
603+     sample_key  =  SecureCacheKey (["delete" , "failure" ], "test_secret" )
604+     sample_data  =  ConnectionInfo (id = "test_connection" , token = "test_token" )
605+ 
606+     # Set data in memory cache only (no file operations due to @mark.nofakefs) 
607+     file_based_cache .memory_cache .set (sample_key , sample_data )
608+ 
609+     # Mock path.exists to return True and os.remove to raise OSError 
610+     with  patch ("firebolt.utils.cache.path.exists" , return_value = True ), patch (
611+         "firebolt.utils.cache.os.remove" 
612+     ) as  mock_remove :
613+         mock_remove .side_effect  =  OSError ("Permission denied" )
614+ 
615+         # Delete should not raise an exception despite file removal failure 
616+         file_based_cache .delete (sample_key )
617+ 
618+         # Verify data is still removed from memory cache 
619+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
620+         assert  memory_result  is  None 
621+ 
622+ 
623+ def  test_file_based_cache_get_from_file_when_not_in_memory (
624+     file_based_cache , encrypter_with_key 
625+ ):
626+     """Test FileBasedCache get method retrieves data from file when not in memory.""" 
627+     # Create test data 
628+     sample_key  =  SecureCacheKey (["file" , "only" ], "test_secret" )
629+     sample_data  =  ConnectionInfo (
630+         id = "test_file_connection" ,
631+         token = "test_file_token" ,
632+         expiry_time = int (time .time ()) +  3600 ,  # Valid for 1 hour 
633+     )
634+ 
635+     # First set data in cache (both memory and file) 
636+     file_based_cache .set (sample_key , sample_data )
637+ 
638+     # Verify data exists 
639+     initial_result  =  file_based_cache .get (sample_key )
640+     assert  initial_result  is  not   None 
641+     assert  initial_result .id  ==  "test_file_connection" 
642+ 
643+     # Clear memory cache but keep file 
644+     file_based_cache .memory_cache .clear ()
645+ 
646+     # Verify memory cache is empty 
647+     memory_result  =  file_based_cache .memory_cache .get (sample_key )
648+     assert  memory_result  is  None 
649+ 
650+     # Verify file still exists 
651+     file_path  =  file_based_cache ._get_file_path (sample_key )
652+     assert  os .path .exists (file_path )
653+ 
654+     # Get should retrieve from file and reload into memory 
655+     file_result  =  file_based_cache .get (sample_key )
656+     assert  file_result  is  not   None 
657+     assert  file_result .id  ==  "test_file_connection" 
658+     assert  file_result .token  ==  "test_file_token" 
659+ 
660+     # Verify data is now back in memory cache 
661+     memory_result_after_load  =  file_based_cache .memory_cache .get (sample_key )
662+     assert  memory_result_after_load  is  not   None 
663+     assert  memory_result_after_load .id  ==  "test_file_connection" 
664+ 
665+ 
666+ def  test_file_based_cache_get_from_corrupted_file (file_based_cache , encrypter_with_key ):
667+     """Test FileBasedCache get method handles corrupted file gracefully.""" 
668+     sample_key  =  SecureCacheKey (["corrupted" , "file" ], "test_secret" )
669+ 
670+     # Create corrupted file manually 
671+     file_path  =  file_based_cache ._get_file_path (sample_key )
672+     os .makedirs (os .path .dirname (file_path ), exist_ok = True )
673+ 
674+     with  open (file_path , "w" ) as  f :
675+         f .write ("corrupted_data_that_cannot_be_decrypted" )
676+ 
677+     # Verify file exists 
678+     assert  os .path .exists (file_path )
679+ 
680+     # Get should return None due to decryption failure 
681+     result  =  file_based_cache .get (sample_key )
682+     assert  result  is  None 
683+ 
684+     # Verify nothing is loaded into memory cache 
685+     memory_result  =  file_based_cache .memory_cache .get (sample_key )
686+     assert  memory_result  is  None 
687+ 
688+ 
689+ def  test_file_based_cache_disabled_behavior (file_based_cache , encrypter_with_key ):
690+     """Test FileBasedCache methods when cache is disabled.""" 
691+     sample_key  =  SecureCacheKey (["disabled" , "test" ], "test_secret" )
692+     sample_data  =  ConnectionInfo (id = "test_connection" , token = "test_token" )
693+ 
694+     # Disable the cache 
695+     file_based_cache .disable ()
696+ 
697+     # Set should do nothing when disabled 
698+     file_based_cache .set (sample_key , sample_data )
699+ 
700+     # Get should return None when disabled 
701+     result  =  file_based_cache .get (sample_key )
702+     assert  result  is  None 
703+ 
704+     # Enable cache, set data, then disable again 
705+     file_based_cache .enable ()
706+     file_based_cache .set (sample_key , sample_data )
707+ 
708+     # Verify data is set 
709+     enabled_result  =  file_based_cache .get (sample_key )
710+     assert  enabled_result  is  not   None 
711+ 
712+     # Disable and verify get returns None 
713+     file_based_cache .disable ()
714+     disabled_result  =  file_based_cache .get (sample_key )
715+     assert  disabled_result  is  None 
716+ 
717+     # Delete should do nothing when disabled 
718+     file_based_cache .delete (sample_key )  # Should not raise exception 
719+ 
720+ 
721+ def  test_file_based_cache_preserves_expiry_from_file (
722+     file_based_cache , encrypter_with_key , fixed_time 
723+ ):
724+     """Test that FileBasedCache preserves original expiry time when loading from file.""" 
725+     sample_key  =  SecureCacheKey (["preserve" , "expiry" ], "test_secret" )
726+ 
727+     # Create data and set it at an earlier time 
728+     sample_data  =  ConnectionInfo (id = "test_connection" )
729+ 
730+     # Set data at fixed_time - this will give it expiry of fixed_time + CACHE_EXPIRY_SECONDS 
731+     with  patch ("time.time" , return_value = fixed_time ):
732+         file_based_cache .set (sample_key , sample_data )
733+ 
734+         # Verify the expiry time that was set 
735+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
736+         expected_expiry  =  fixed_time  +  CACHE_EXPIRY_SECONDS 
737+         assert  memory_result .expiry_time  ==  expected_expiry 
738+ 
739+         # Clear memory cache to force file load on next get 
740+         file_based_cache .memory_cache .clear ()
741+ 
742+         # Get data from file (should preserve the original expiry time from file) 
743+         result  =  file_based_cache .get (sample_key )
744+ 
745+         assert  result  is  not   None 
746+         assert  (
747+             result .expiry_time  ==  expected_expiry 
748+         )  # Should preserve original expiry from file 
749+         assert  result .id  ==  "test_connection" 
750+ 
751+         # Verify it's also in memory cache with preserved expiry 
752+         memory_result_after_load  =  file_based_cache .memory_cache .get (sample_key )
753+         assert  memory_result_after_load  is  not   None 
754+         assert  memory_result_after_load .expiry_time  ==  expected_expiry 
755+ 
756+ 
757+ def  test_file_based_cache_deletes_expired_file_on_get (
758+     file_based_cache , encrypter_with_key , fixed_time 
759+ ):
760+     """Test that FileBasedCache deletes expired files on get and returns cache miss.""" 
761+     sample_key  =  SecureCacheKey (["expired" , "file" ], "test_secret" )
762+     sample_data  =  ConnectionInfo (id = "test_connection" )
763+ 
764+     # Set data at an early time so it gets an early expiry 
765+     early_time  =  fixed_time  -  7200   # 2 hours before 
766+     with  patch ("time.time" , return_value = early_time ):
767+         file_based_cache .set (sample_key , sample_data )
768+ 
769+         # Verify the expiry time that was set (should be early_time + CACHE_EXPIRY_SECONDS) 
770+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
771+         expected_expiry  =  early_time  +  CACHE_EXPIRY_SECONDS 
772+         assert  memory_result .expiry_time  ==  expected_expiry 
773+ 
774+     # Verify file was created 
775+     file_path  =  file_based_cache ._get_file_path (sample_key )
776+     assert  os .path .exists (file_path )
777+ 
778+     # Clear memory cache to force file load 
779+     file_based_cache .memory_cache .clear ()
780+ 
781+     # Now try to get at a time when the data should be expired 
782+     # The data expires at early_time + CACHE_EXPIRY_SECONDS 
783+     # Let's try to get it after that expiry time 
784+     expired_check_time  =  early_time  +  CACHE_EXPIRY_SECONDS  +  1 
785+     with  patch ("time.time" , return_value = expired_check_time ):
786+         result  =  file_based_cache .get (sample_key )
787+ 
788+         # Should return None due to expiry 
789+         assert  result  is  None 
790+ 
791+         # File should be deleted 
792+         assert  not  os .path .exists (file_path )
793+ 
794+         # Memory cache should not contain the data 
795+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
796+         assert  memory_result  is  None 
797+ 
798+ 
799+ def  test_file_based_cache_expiry_edge_case_exactly_expired (
800+     file_based_cache , encrypter_with_key , fixed_time 
801+ ):
802+     """Test behavior when data expires exactly at the current time.""" 
803+     sample_key  =  SecureCacheKey (["edge" , "case" ], "test_secret" )
804+     sample_data  =  ConnectionInfo (id = "test_connection" )
805+ 
806+     # Set data such that it will expire exactly at fixed_time 
807+     set_time  =  fixed_time  -  CACHE_EXPIRY_SECONDS 
808+     with  patch ("time.time" , return_value = set_time ):
809+         file_based_cache .set (sample_key , sample_data )
810+ 
811+         # Verify the expiry time that was set 
812+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
813+         expected_expiry  =  set_time  +  CACHE_EXPIRY_SECONDS   # This equals fixed_time 
814+         assert  memory_result .expiry_time  ==  expected_expiry  ==  fixed_time 
815+ 
816+     file_path  =  file_based_cache ._get_file_path (sample_key )
817+     assert  os .path .exists (file_path )
818+ 
819+     # Clear memory cache 
820+     file_based_cache .memory_cache .clear ()
821+ 
822+     # Try to get exactly at expiry time (should be considered expired) 
823+     with  patch ("time.time" , return_value = fixed_time ):
824+         result  =  file_based_cache .get (sample_key )
825+ 
826+         # Should return None as data is expired (>= check in _is_expired) 
827+         assert  result  is  None 
828+ 
829+         # File should be deleted 
830+         assert  not  os .path .exists (file_path )
831+ 
832+ 
833+ def  test_file_based_cache_non_expired_file_loads_correctly (
834+     file_based_cache , encrypter_with_key , fixed_time 
835+ ):
836+     """Test that non-expired data from file loads correctly with preserved expiry.""" 
837+     sample_key  =  SecureCacheKey (["non" , "expired" ], "test_secret" )
838+ 
839+     sample_data  =  ConnectionInfo (id = "test_connection" , token = "test_token" )
840+ 
841+     # Set data at an earlier time so it's not expired yet 
842+     set_time  =  fixed_time  -  900   # 15 minutes before 
843+     with  patch ("time.time" , return_value = set_time ):
844+         file_based_cache .set (sample_key , sample_data )
845+ 
846+         # Verify expiry time 
847+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
848+         expected_expiry  =  set_time  +  CACHE_EXPIRY_SECONDS 
849+         assert  memory_result .expiry_time  ==  expected_expiry 
850+ 
851+     # Clear memory cache to force file load 
852+     file_based_cache .memory_cache .clear ()
853+ 
854+     # Get data at fixed_time (data should not be expired since expected_expiry > fixed_time) 
855+     with  patch ("time.time" , return_value = fixed_time ):
856+         # Ensure the data is not expired 
857+         assert  expected_expiry  >  fixed_time , "Data should not be expired for this test" 
858+ 
859+         result  =  file_based_cache .get (sample_key )
860+ 
861+         # Should successfully load data 
862+         assert  result  is  not   None 
863+         assert  result .id  ==  "test_connection" 
864+         assert  result .token  ==  "test_token" 
865+         assert  result .expiry_time  ==  expected_expiry   # Preserved original expiry 
866+ 
867+         # Verify file still exists (not deleted) 
868+         file_path  =  file_based_cache ._get_file_path (sample_key )
869+         assert  os .path .exists (file_path )
870+ 
871+         # Verify it's in memory cache with preserved expiry 
872+         memory_result  =  file_based_cache .memory_cache .get (sample_key )
873+         assert  memory_result  is  not   None 
874+         assert  memory_result .expiry_time  ==  expected_expiry 
875+ 
876+ 
877+ def  test_memory_cache_set_preserve_expiry_parameter (
878+     cache , sample_cache_key , fixed_time 
879+ ):
880+     """Test UtilCache.set preserve_expiry parameter functionality.""" 
881+     # Create connection info with specific expiry time 
882+     original_expiry  =  fixed_time  +  1800 
883+     sample_data  =  ConnectionInfo (id = "test_connection" , expiry_time = original_expiry )
884+ 
885+     with  patch ("time.time" , return_value = fixed_time ):
886+         # Test preserve_expiry=True 
887+         cache .set (sample_cache_key , sample_data , preserve_expiry = True )
888+ 
889+         result  =  cache .get (sample_cache_key )
890+         assert  result  is  not   None 
891+         assert  result .expiry_time  ==  original_expiry   # Should preserve original 
892+ 
893+         cache .clear ()
894+ 
895+         # Test preserve_expiry=False (default behavior) 
896+         cache .set (sample_cache_key , sample_data , preserve_expiry = False )
897+ 
898+         result  =  cache .get (sample_cache_key )
899+         assert  result  is  not   None 
900+         expected_new_expiry  =  fixed_time  +  CACHE_EXPIRY_SECONDS 
901+         assert  result .expiry_time  ==  expected_new_expiry   # Should get new expiry 
902+ 
903+         cache .clear ()
904+ 
905+         # Test default behavior (preserve_expiry not specified) 
906+         cache .set (sample_cache_key , sample_data )
907+ 
908+         result  =  cache .get (sample_cache_key )
909+         assert  result  is  not   None 
910+         assert  result .expiry_time  ==  expected_new_expiry   # Should get new expiry 
911+ 
912+ 
913+ def  test_memory_cache_set_preserve_expiry_with_none_expiry (
914+     cache , sample_cache_key , fixed_time 
915+ ):
916+     """Test UtilCache.set preserve_expiry when original expiry_time is None.""" 
917+     # Create connection info with None expiry time 
918+     sample_data  =  ConnectionInfo (id = "test_connection" , expiry_time = None )
919+ 
920+     with  patch ("time.time" , return_value = fixed_time ):
921+         # Even with preserve_expiry=True, None expiry should get new expiry 
922+         cache .set (sample_cache_key , sample_data , preserve_expiry = True )
923+ 
924+         result  =  cache .get (sample_cache_key )
925+         assert  result  is  not   None 
926+         expected_expiry  =  fixed_time  +  CACHE_EXPIRY_SECONDS 
927+         assert  (
928+             result .expiry_time  ==  expected_expiry 
929+         )  # Should get new expiry despite preserve=True 
0 commit comments