APIKeyManager.load() decrypts every stored key with a dict comprehension and no error handling. If the .key file no longer matches the ciphertext in api_keys.json — key rotated, a partial/!mismatched data restore, or a corrupted .key — Fernet.decrypt raises cryptography.fernet.InvalidToken. app_initializer.py calls api_key_manager.load() during startup, so a single undecryptable entry takes down the whole app at boot, and the user can't reach the UI to fix it. Decrypt each key in a loop and, on InvalidToken/ValueError, log a warning and skip that one entry while still returning every key that decrypts cleanly. One bad/stale key no longer blocks startup. tests/test_api_key_manager_resilience.py saves a valid key, then injects an entry encrypted under a different Fernet key (InvalidToken) and a malformed token (ValueError), and asserts load() returns the good key and skips the bad ones without raising. Fails before this change.
36 lines
1.3 KiB
Python
36 lines
1.3 KiB
Python
import os
|
|
import json
|
|
from src.api_key_manager import APIKeyManager
|
|
from cryptography.fernet import Fernet
|
|
|
|
def test_api_key_manager_load_resilience(tmp_path):
|
|
mgr = APIKeyManager(str(tmp_path))
|
|
|
|
# Save a valid key
|
|
mgr.save("good_provider", "good_value")
|
|
|
|
# Create another key manager/Fernet instance with a different key to produce an undecryptable token
|
|
other_key = Fernet.generate_key()
|
|
other_f = Fernet(other_key)
|
|
undecryptable_token = other_f.encrypt(b"bad_value").decode()
|
|
|
|
# Manually edit api_keys.json to include the undecryptable token
|
|
with open(mgr.api_keys_file, "r", encoding="utf-8") as f:
|
|
keys = json.load(f)
|
|
|
|
keys["bad_provider"] = undecryptable_token
|
|
# Also add a malformed/garbage token (causes ValueError/binascii.Error)
|
|
keys["garbage_provider"] = "not-a-valid-base64-fernet-token"
|
|
|
|
with open(mgr.api_keys_file, "w", encoding="utf-8") as f:
|
|
json.dump(keys, f)
|
|
|
|
# Load keys
|
|
loaded = mgr.load()
|
|
|
|
# Assert load() returns the still-decryptable key and skips the bad ones without raising
|
|
assert "good_provider" in loaded
|
|
assert loaded["good_provider"] == "good_value"
|
|
assert "bad_provider" not in loaded
|
|
assert "garbage_provider" not in loaded
|