Skip to content

Commit 90a5b4d

Browse files
committed
additional tests
1 parent c670097 commit 90a5b4d

File tree

1 file changed

+367
-0
lines changed

1 file changed

+367
-0
lines changed

tests/unit/utils/test_cache.py

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)