Added support for all Redis versions (>=1.0.0) #5
					 4 changed files with 84 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
from functools import lru_cache
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from cryptography.fernet import Fernet
 | 
			
		||||
from redis.asyncio import Redis
 | 
			
		||||
from redis.exceptions import ResponseError
 | 
			
		||||
from redis.typing import ResponseT
 | 
			
		||||
 | 
			
		||||
from pssecret_server.models import Secret
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,3 +33,26 @@ async def save_secret(data: Secret, redis: Redis) -> str:
 | 
			
		|||
    await redis.setex(new_key, 60 * 60 * 24, data.data)
 | 
			
		||||
 | 
			
		||||
    return new_key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@lru_cache
 | 
			
		||||
async def _is_getdel_available(redis: Redis) -> bool:
 | 
			
		||||
    """GETDEL is not available in Redis prior to version 6.2"""
 | 
			
		||||
    try:
 | 
			
		||||
        await redis.getdel("test:getdel:availability")
 | 
			
		||||
    except ResponseError:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def getdel(redis: Redis, key: str) -> ResponseT:
 | 
			
		||||
    result: ResponseT
 | 
			
		||||
 | 
			
		||||
    if await _is_getdel_available(redis):
 | 
			
		||||
        result = await redis.getdel(key)
 | 
			
		||||
    else:
 | 
			
		||||
        result = await redis.getset(key, "")
 | 
			
		||||
        await redis.delete(key)
 | 
			
		||||
 | 
			
		||||
    return result
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,3 +53,6 @@ reportUnusedCallResult = "none"
 | 
			
		|||
 | 
			
		||||
[tool.pytest.ini_options]
 | 
			
		||||
asyncio_mode = "auto"
 | 
			
		||||
 | 
			
		||||
[tool.isort]
 | 
			
		||||
profile = "black"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,8 @@
 | 
			
		|||
from unittest.mock import patch
 | 
			
		||||
from unittest.mock import AsyncMock, Mock, patch
 | 
			
		||||
 | 
			
		||||
from redis.asyncio import Redis
 | 
			
		||||
 | 
			
		||||
from pssecret_server.utils import get_new_key, save_secret
 | 
			
		||||
from pssecret_server.utils import get_new_key, getdel, save_secret
 | 
			
		||||
 | 
			
		||||
from ..factories import SecretFactory
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -33,3 +33,35 @@ async def test_save_secret_data(redis_server: Redis) -> None:
 | 
			
		|||
 | 
			
		||||
    assert redis_data is not None
 | 
			
		||||
    assert redis_data.decode() == secret.data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch("pssecret_server.utils._is_getdel_available", side_effect=AsyncMock())
 | 
			
		||||
async def test_getdel_when_available(
 | 
			
		||||
    is_getdel_available: Mock, redis_server: Redis
 | 
			
		||||
) -> None:
 | 
			
		||||
    is_getdel_available.side_effect.return_value = True
 | 
			
		||||
 | 
			
		||||
    test_value = "test_data"
 | 
			
		||||
    test_key = "test_key"
 | 
			
		||||
    await redis_server.set(test_key, test_value)
 | 
			
		||||
 | 
			
		||||
    result = await getdel(redis_server, test_key)
 | 
			
		||||
 | 
			
		||||
    assert result.decode() == test_value
 | 
			
		||||
    assert not await redis_server.exists(test_key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch("pssecret_server.utils._is_getdel_available", side_effect=AsyncMock())
 | 
			
		||||
async def test_getdel_when_not_available(
 | 
			
		||||
    is_getdel_available: Mock, redis_server: Redis
 | 
			
		||||
) -> None:
 | 
			
		||||
    is_getdel_available.side_effect.return_value = False
 | 
			
		||||
 | 
			
		||||
    test_value = "test_data"
 | 
			
		||||
    test_key = "test_key"
 | 
			
		||||
    await redis_server.set(test_key, test_value)
 | 
			
		||||
 | 
			
		||||
    result = await getdel(redis_server, test_key)
 | 
			
		||||
 | 
			
		||||
    assert result.decode() == test_value
 | 
			
		||||
    assert not await redis_server.exists(test_key)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
from unittest.mock import AsyncMock
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from cryptography.fernet import Fernet, InvalidToken
 | 
			
		||||
from redis.exceptions import ResponseError
 | 
			
		||||
 | 
			
		||||
from pssecret_server.utils import decrypt_secret, encrypt_secret
 | 
			
		||||
from pssecret_server.utils import _is_getdel_available, decrypt_secret, encrypt_secret
 | 
			
		||||
 | 
			
		||||
from ..factories import SecretFactory
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +32,20 @@ def test_secret_is_not_decryptable_by_random_key(fernet: Fernet):
 | 
			
		|||
 | 
			
		||||
    with pytest.raises(InvalidToken):
 | 
			
		||||
        decrypt_secret(encrypted_secret.data.encode(), random_fernet)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def test_is_getdel_available_when_supported():
 | 
			
		||||
    redis = AsyncMock()
 | 
			
		||||
 | 
			
		||||
    result = await _is_getdel_available(redis)
 | 
			
		||||
 | 
			
		||||
    assert result is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def test_is_getdel_available_when_not_supported():
 | 
			
		||||
    redis = AsyncMock()
 | 
			
		||||
    redis.getdel.side_effect = ResponseError
 | 
			
		||||
 | 
			
		||||
    result = await _is_getdel_available(redis)
 | 
			
		||||
 | 
			
		||||
    assert result is False
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue