|
1 | 1 | import os |
2 | 2 | import unittest |
3 | 3 | from datetime import datetime |
4 | | -from unittest.mock import AsyncMock, MagicMock, patch |
| 4 | +from io import StringIO |
| 5 | +from unittest.mock import MagicMock, patch |
5 | 6 |
|
6 | 7 | import pytest |
7 | | -from aiohttp import ClientError, ClientResponseError |
| 8 | +import requests |
8 | 9 | from dune_client.types import QueryParameter |
9 | 10 |
|
10 | 11 | from src.config import Env, RuntimeConfig |
@@ -64,6 +65,32 @@ def setUpClass(cls): |
64 | 65 | def tearDownClass(cls): |
65 | 66 | cls.env_patcher.stop() |
66 | 67 |
|
| 68 | + def test_is_url(self): |
| 69 | + # Valid URLs |
| 70 | + assert RuntimeConfig._is_url("https://example.com") is True |
| 71 | + assert RuntimeConfig._is_url("http://localhost:8080") is True |
| 72 | + assert RuntimeConfig._is_url("ftp://files.example.com") is True |
| 73 | + assert RuntimeConfig._is_url("https://api.github.com/path?query=123") is True |
| 74 | + assert RuntimeConfig._is_url("sftp://user:pass@server.com:22") is True |
| 75 | + |
| 76 | + # Invalid URLs |
| 77 | + assert RuntimeConfig._is_url("not-a-url") is False |
| 78 | + assert RuntimeConfig._is_url("") is False |
| 79 | + assert RuntimeConfig._is_url("file.txt") is False |
| 80 | + assert RuntimeConfig._is_url("/path/to/file") is False |
| 81 | + assert RuntimeConfig._is_url("C:\\Windows\\Path") is False |
| 82 | + assert RuntimeConfig._is_url("://missing-scheme.com") is False |
| 83 | + assert RuntimeConfig._is_url("http://") is False # Missing netloc |
| 84 | + |
| 85 | + # Edge cases |
| 86 | + assert RuntimeConfig._is_url(None) is False # type: ignore |
| 87 | + assert RuntimeConfig._is_url("http:/example.com") is False # Missing slash |
| 88 | + assert RuntimeConfig._is_url("https:example.com") is False # Missing slashes |
| 89 | + |
| 90 | + # Cases that actually trigger exceptions |
| 91 | + assert RuntimeConfig._is_url([1, 2, 3]) is False # TypeError: list is not str |
| 92 | + assert RuntimeConfig._is_url(123) is False # TypeError: int is not str |
| 93 | + |
67 | 94 | def test_load_basic_conf(self): |
68 | 95 | config_file = config_root / "basic.yaml" |
69 | 96 | conf = RuntimeConfig.load(config_file.absolute()) |
@@ -124,58 +151,63 @@ def test_load_buggy_conf(self): |
124 | 151 | with self.assertRaises(SystemExit): |
125 | 152 | RuntimeConfig.load(config_root / "no_data_sources.yaml") |
126 | 153 |
|
127 | | - @pytest.mark.asyncio |
128 | | - async def test_successful_download(self): |
129 | | - mock_response = AsyncMock(name="Mock GET Response") |
130 | | - mock_response.text = AsyncMock(return_value="test_config_content") |
131 | | - mock_response.raise_for_status.return_value = True |
132 | | - mock_get = AsyncMock() |
133 | | - mock_get.__aenter__.return_value = mock_response |
134 | | - |
135 | | - with patch("src.config.ClientSession.get", return_value=mock_get): |
136 | | - result = await RuntimeConfig._download_config("http://test.xyz") |
137 | | - |
138 | | - self.assertEqual("test_config_content", result) |
139 | | - mock_response.raise_for_status.assert_called_once() |
140 | | - mock_response.text.assert_called_once() |
141 | | - |
142 | | - @pytest.mark.asyncio |
143 | | - async def test_http_error_response(self): |
144 | | - error_response = ClientResponseError( |
145 | | - request_info=None, history=None, status=404, message="Not Found" |
146 | | - ) |
147 | | - mock_response = AsyncMock(name="Mock GET Response") |
148 | | - mock_response.raise_for_status = MagicMock( |
149 | | - side_effect=error_response, name="mock raise for status" |
150 | | - ) |
151 | | - mock_get = AsyncMock() |
152 | | - mock_get.__aenter__.return_value = mock_response |
153 | | - |
| 154 | + with self.assertRaises(ValueError): |
| 155 | + RuntimeConfig.load(config_root / "invalid_request_timeout.yaml") |
| 156 | + |
| 157 | + def test_load_config_url(self): |
| 158 | + # Mock response for successful case |
| 159 | + mock_yaml_content = """ |
| 160 | + data_sources: |
| 161 | + - name: test |
| 162 | + type: dune |
| 163 | + key: test_key |
| 164 | + jobs: |
| 165 | + - name: job1 |
| 166 | + source: |
| 167 | + ref: test |
| 168 | + """ |
| 169 | + |
| 170 | + # Test successful download |
154 | 171 | with ( |
155 | | - patch("src.config.log") as mock_logger, |
156 | | - patch("src.config.ClientSession.get", return_value=mock_get), |
| 172 | + patch("requests.get") as mock_get, |
| 173 | + patch("src.config.RuntimeConfig.read_yaml") as mock_read_yaml, |
157 | 174 | ): |
158 | | - result = await RuntimeConfig._download_config( |
159 | | - "http://test.thistldbetternotexist" |
160 | | - ) |
| 175 | + # Setup mock response |
| 176 | + mock_response = MagicMock() |
| 177 | + mock_response.text = mock_yaml_content |
| 178 | + mock_response.raise_for_status.return_value = None |
| 179 | + mock_get.return_value = mock_response |
161 | 180 |
|
162 | | - self.assertIsNone(result) |
163 | | - mock_logger.error.assert_called_once_with( |
164 | | - "Error fetching config from URL: %s", error_response |
165 | | - ) |
| 181 | + mock_read_yaml.return_value = {"test": "data"} |
166 | 182 |
|
167 | | - @pytest.mark.asyncio |
168 | | - async def test_client_connection_error(self): |
169 | | - with ( |
170 | | - patch("aiohttp.ClientSession", side_effect=ClientError("Connection error")), |
171 | | - patch("src.config.log") as mock_logger, |
172 | | - ): |
173 | | - result = await RuntimeConfig._download_config( |
174 | | - "http://test.thistldbetternotexist" |
| 183 | + result = RuntimeConfig._load_config_url("https://example.com/config.yaml") |
| 184 | + |
| 185 | + # Verify the URL was called with timeout |
| 186 | + mock_get.assert_called_once_with( |
| 187 | + "https://example.com/config.yaml", timeout=10 |
175 | 188 | ) |
| 189 | + # Verify read_yaml was called with StringIO containing our mock content |
| 190 | + mock_read_yaml.assert_called_once() |
| 191 | + assert isinstance(mock_read_yaml.call_args[0][0], StringIO) |
| 192 | + assert result == {"test": "data"} |
| 193 | + |
| 194 | + # Test HTTP error |
| 195 | + with patch("requests.get") as mock_get: |
| 196 | + mock_response = MagicMock() |
| 197 | + mock_response.raise_for_status.side_effect = requests.HTTPError( |
| 198 | + "404 Not Found" |
| 199 | + ) |
| 200 | + mock_get.return_value = mock_response |
| 201 | + |
| 202 | + with pytest.raises(SystemExit, match="Could not download config"): |
| 203 | + RuntimeConfig._load_config_url("https://example.com/config.yaml") |
| 204 | + |
| 205 | + # Test network error |
| 206 | + with patch("requests.get") as mock_get: |
| 207 | + mock_get.side_effect = requests.RequestException("Network error") |
176 | 208 |
|
177 | | - assert result is None |
178 | | - mock_logger.error.assert_called_once() |
| 209 | + with pytest.raises(SystemExit, match="Could not download config"): |
| 210 | + RuntimeConfig._load_config_url("https://example.com/config.yaml") |
179 | 211 |
|
180 | 212 |
|
181 | 213 | class TestParseQueryParameters(unittest.TestCase): |
|
0 commit comments