|  | 
| 3 | 3 | import uuid | 
| 4 | 4 | from copy import deepcopy | 
| 5 | 5 | from datetime import datetime, timedelta | 
| 6 |  | -from unittest.mock import patch | 
|  | 6 | +from unittest.mock import AsyncMock, MagicMock, patch | 
| 7 | 7 | 
 | 
| 8 | 8 | import pytest | 
| 9 | 9 | 
 | 
|  | 10 | +from stac_fastapi.core.redis_utils import ( | 
|  | 11 | +    RedisSentinelSettings, | 
|  | 12 | +    RedisSettings, | 
|  | 13 | +    connect_redis, | 
|  | 14 | +    connect_redis_sentinel, | 
|  | 15 | +    get_prev_link, | 
|  | 16 | +    save_self_link, | 
|  | 17 | +) | 
| 10 | 18 | from stac_fastapi.types.errors import ConflictError | 
| 11 | 19 | 
 | 
| 12 | 20 | from ..conftest import create_collection, create_item | 
| @@ -1625,3 +1633,126 @@ async def test_use_datetime_false(app_client, load_test_data, txn_client, monkey | 
| 1625 | 1633 | 
 | 
| 1626 | 1634 |     assert "test-item-datetime-only" not in found_ids | 
| 1627 | 1635 |     assert "test-item-start-end-only" in found_ids | 
|  | 1636 | + | 
|  | 1637 | + | 
|  | 1638 | +@pytest.mark.asyncio | 
|  | 1639 | +async def test_connect_redis(): | 
|  | 1640 | +    from stac_fastapi.core import redis_utils | 
|  | 1641 | + | 
|  | 1642 | +    redis_utils.redis_pool = None | 
|  | 1643 | + | 
|  | 1644 | +    test_settings = RedisSettings( | 
|  | 1645 | +        REDIS_HOST="test-redis-host", | 
|  | 1646 | +        REDIS_PORT=6380, | 
|  | 1647 | +        REDIS_DB=5, | 
|  | 1648 | +        REDIS_MAX_CONNECTIONS=20, | 
|  | 1649 | +        REDIS_RETRY_TIMEOUT=False, | 
|  | 1650 | +        REDIS_DECODE_RESPONSES=False, | 
|  | 1651 | +        REDIS_CLIENT_NAME="custom-client", | 
|  | 1652 | +        REDIS_HEALTH_CHECK_INTERVAL=50, | 
|  | 1653 | +    ) | 
|  | 1654 | + | 
|  | 1655 | +    with patch( | 
|  | 1656 | +        "stac_fastapi.core.redis_utils.aioredis.ConnectionPool" | 
|  | 1657 | +    ) as mock_pool_class, patch( | 
|  | 1658 | +        "stac_fastapi.core.redis_utils.aioredis.Redis" | 
|  | 1659 | +    ) as mock_redis_class: | 
|  | 1660 | + | 
|  | 1661 | +        mock_pool_instance = AsyncMock() | 
|  | 1662 | +        mock_redis_instance = AsyncMock() | 
|  | 1663 | +        mock_pool_class.return_value = mock_pool_instance | 
|  | 1664 | +        mock_redis_class.return_value = mock_redis_instance | 
|  | 1665 | + | 
|  | 1666 | +        result = await connect_redis(test_settings) | 
|  | 1667 | + | 
|  | 1668 | +        mock_pool_class.assert_called_once_with( | 
|  | 1669 | +            host="test-redis-host", | 
|  | 1670 | +            port=6380, | 
|  | 1671 | +            db=5, | 
|  | 1672 | +            max_connections=20, | 
|  | 1673 | +            decode_responses=False, | 
|  | 1674 | +            retry_on_timeout=False, | 
|  | 1675 | +            health_check_interval=50, | 
|  | 1676 | +        ) | 
|  | 1677 | + | 
|  | 1678 | +        mock_redis_class.assert_called_once_with( | 
|  | 1679 | +            connection_pool=mock_pool_instance, client_name="custom-client" | 
|  | 1680 | +        ) | 
|  | 1681 | + | 
|  | 1682 | +        assert result == mock_redis_instance | 
|  | 1683 | + | 
|  | 1684 | + | 
|  | 1685 | +@pytest.mark.asyncio | 
|  | 1686 | +async def test_connect_redis_sentinel(monkeypatch): | 
|  | 1687 | +    from stac_fastapi.core import redis_utils | 
|  | 1688 | + | 
|  | 1689 | +    redis_utils.redis_pool = None | 
|  | 1690 | + | 
|  | 1691 | +    master_mock = AsyncMock() | 
|  | 1692 | + | 
|  | 1693 | +    sentinel_mock = MagicMock() | 
|  | 1694 | +    sentinel_mock.master_for.return_value = master_mock | 
|  | 1695 | + | 
|  | 1696 | +    with patch("stac_fastapi.core.redis_utils.Sentinel") as mock_sentinel_class: | 
|  | 1697 | +        mock_sentinel_class.return_value = sentinel_mock | 
|  | 1698 | + | 
|  | 1699 | +        settings = RedisSentinelSettings( | 
|  | 1700 | +            REDIS_SENTINEL_HOSTS="test-redis-sentinel-host", | 
|  | 1701 | +            REDIS_SENTINEL_PORTS="26379", | 
|  | 1702 | +            REDIS_SENTINEL_MASTER_NAME="master", | 
|  | 1703 | +            REDIS_DB=15, | 
|  | 1704 | +            REDIS_MAX_CONNECTIONS=20, | 
|  | 1705 | +            REDIS_RETRY_TIMEOUT=False, | 
|  | 1706 | +            REDIS_DECODE_RESPONSES=False, | 
|  | 1707 | +            REDIS_CLIENT_NAME="custom-client", | 
|  | 1708 | +            REDIS_HEALTH_CHECK_INTERVAL=50, | 
|  | 1709 | +        ) | 
|  | 1710 | + | 
|  | 1711 | +        redis = await connect_redis_sentinel(settings) | 
|  | 1712 | + | 
|  | 1713 | +        mock_sentinel_class.assert_called_once_with( | 
|  | 1714 | +            [("test-redis-sentinel-host", 26379)], | 
|  | 1715 | +            decode_responses=False, | 
|  | 1716 | +        ) | 
|  | 1717 | + | 
|  | 1718 | +        sentinel_mock.master_for.assert_called_once_with( | 
|  | 1719 | +            service_name="master", | 
|  | 1720 | +            db=15, | 
|  | 1721 | +            decode_responses=False, | 
|  | 1722 | +            retry_on_timeout=False, | 
|  | 1723 | +            client_name="custom-client", | 
|  | 1724 | +            max_connections=20, | 
|  | 1725 | +            health_check_interval=50, | 
|  | 1726 | +        ) | 
|  | 1727 | + | 
|  | 1728 | +        assert redis is master_mock | 
|  | 1729 | +        assert redis_utils.redis_pool is master_mock | 
|  | 1730 | + | 
|  | 1731 | + | 
|  | 1732 | +@pytest.mark.asyncio | 
|  | 1733 | +async def test_save_and_get_prev_link(): | 
|  | 1734 | +    mock_redis = AsyncMock() | 
|  | 1735 | + | 
|  | 1736 | +    await save_self_link(mock_redis, "dummy_token", "http://mywebsite.com/page1") | 
|  | 1737 | +    mock_redis.setex.assert_awaited_once_with( | 
|  | 1738 | +        "nav:self:dummy_token", 1800, "http://mywebsite.com/page1" | 
|  | 1739 | +    ) | 
|  | 1740 | + | 
|  | 1741 | +    mock_redis.reset_mock() | 
|  | 1742 | +    mock_redis.get.return_value = "http://mywebsite.com/page1" | 
|  | 1743 | + | 
|  | 1744 | +    result = await get_prev_link(mock_redis, "dummy_token") | 
|  | 1745 | +    assert result == "http://mywebsite.com/page1" | 
|  | 1746 | +    mock_redis.get.assert_awaited_once_with("nav:self:dummy_token") | 
|  | 1747 | + | 
|  | 1748 | +    mock_redis.reset_mock() | 
|  | 1749 | +    result_none = await get_prev_link(mock_redis, None) | 
|  | 1750 | +    assert result_none is None | 
|  | 1751 | +    mock_redis.get.assert_not_called() | 
|  | 1752 | + | 
|  | 1753 | +    mock_redis.reset_mock() | 
|  | 1754 | +    mock_redis.get.return_value = None | 
|  | 1755 | + | 
|  | 1756 | +    result_missing = await get_prev_link(mock_redis, "dummy_token_2") | 
|  | 1757 | +    assert result_missing is None | 
|  | 1758 | +    mock_redis.get.assert_awaited_once_with("nav:self:dummy_token_2") | 
0 commit comments