|
14 | 14 | # KIND, either express or implied. See the License for the |
15 | 15 | # specific language governing permissions and limitations |
16 | 16 | # under the License. |
| 17 | +from typing import Any |
17 | 18 | from unittest.mock import MagicMock, patch |
18 | 19 |
|
19 | 20 | import pandas as pd |
20 | 21 | import pytest |
21 | 22 |
|
22 | 23 | from superset.common.query_actions import _detect_currency |
| 24 | +from superset.viz import BaseViz |
23 | 25 |
|
24 | 26 |
|
25 | 27 | @pytest.fixture |
@@ -50,6 +52,24 @@ def mock_datasource() -> MagicMock: |
50 | 52 | return ds |
51 | 53 |
|
52 | 54 |
|
| 55 | +@pytest.fixture |
| 56 | +def base_query_obj() -> dict[str, Any]: |
| 57 | + return { |
| 58 | + "filter": [], |
| 59 | + "granularity": None, |
| 60 | + "from_dttm": None, |
| 61 | + "to_dttm": None, |
| 62 | + "extras": {}, |
| 63 | + } |
| 64 | + |
| 65 | + |
| 66 | +def _build_viz(form_data: dict[str, Any], datasource: MagicMock) -> BaseViz: |
| 67 | + viz = BaseViz.__new__(BaseViz) |
| 68 | + viz.form_data = form_data |
| 69 | + viz.datasource = datasource |
| 70 | + return viz |
| 71 | + |
| 72 | + |
53 | 73 | def test_detect_currency_returns_none_when_form_data_is_none( |
54 | 74 | mock_query_obj: MagicMock, |
55 | 75 | mock_datasource: MagicMock, |
@@ -157,3 +177,174 @@ def test_detect_currency_queries_datasource_when_column_not_in_df( |
157 | 177 |
|
158 | 178 | assert result == "GBP" |
159 | 179 | mock_detect.assert_called_once() |
| 180 | + |
| 181 | + |
| 182 | +# Tests for column_config AUTO detection (Table charts) |
| 183 | + |
| 184 | + |
| 185 | +@pytest.fixture |
| 186 | +def mock_query_context_with_column_config() -> MagicMock: |
| 187 | + """Create a mock QueryContext with column_config AUTO currency (Table charts).""" |
| 188 | + context = MagicMock() |
| 189 | + context.form_data = { |
| 190 | + "column_config": { |
| 191 | + "cost": {"currencyFormat": {"symbol": "AUTO", "symbolPosition": "prefix"}} |
| 192 | + } |
| 193 | + } |
| 194 | + return context |
| 195 | + |
| 196 | + |
| 197 | +@patch("superset.common.query_actions.detect_currency") |
| 198 | +def test_detect_currency_checks_column_config_for_auto( |
| 199 | + mock_detect: MagicMock, |
| 200 | + mock_query_context_with_column_config: MagicMock, |
| 201 | + mock_query_obj: MagicMock, |
| 202 | + mock_datasource: MagicMock, |
| 203 | +) -> None: |
| 204 | + """Runs detection when column_config has AUTO currency (Table charts).""" |
| 205 | + mock_detect.return_value = "USD" |
| 206 | + |
| 207 | + result = _detect_currency( |
| 208 | + mock_query_context_with_column_config, |
| 209 | + mock_query_obj, |
| 210 | + mock_datasource, |
| 211 | + ) |
| 212 | + |
| 213 | + assert result == "USD" |
| 214 | + mock_detect.assert_called_once() |
| 215 | + |
| 216 | + |
| 217 | +@patch("superset.common.query_actions.detect_currency_from_df") |
| 218 | +def test_detect_currency_column_config_uses_dataframe( |
| 219 | + mock_detect_from_df: MagicMock, |
| 220 | + mock_query_context_with_column_config: MagicMock, |
| 221 | + mock_query_obj: MagicMock, |
| 222 | + mock_datasource: MagicMock, |
| 223 | +) -> None: |
| 224 | + """Uses dataframe detection when column_config has AUTO and df has currency.""" |
| 225 | + df = pd.DataFrame({"currency_code": ["EUR", "EUR"]}) |
| 226 | + mock_detect_from_df.return_value = "EUR" |
| 227 | + |
| 228 | + result = _detect_currency( |
| 229 | + mock_query_context_with_column_config, |
| 230 | + mock_query_obj, |
| 231 | + mock_datasource, |
| 232 | + df, |
| 233 | + ) |
| 234 | + |
| 235 | + assert result == "EUR" |
| 236 | + mock_detect_from_df.assert_called_once_with(df, "currency_code") |
| 237 | + |
| 238 | + |
| 239 | +def test_detect_currency_skips_when_no_auto_in_column_config( |
| 240 | + mock_query_obj: MagicMock, |
| 241 | + mock_datasource: MagicMock, |
| 242 | +) -> None: |
| 243 | + """Returns None when column_config has explicit currency (not AUTO).""" |
| 244 | + context = MagicMock() |
| 245 | + context.form_data = { |
| 246 | + "column_config": {"cost": {"currencyFormat": {"symbol": "USD"}}} |
| 247 | + } |
| 248 | + |
| 249 | + result = _detect_currency(context, mock_query_obj, mock_datasource) |
| 250 | + |
| 251 | + assert result is None |
| 252 | + |
| 253 | + |
| 254 | +@patch("superset.common.query_actions.detect_currency") |
| 255 | +def test_detect_currency_works_with_both_top_level_and_column_config( |
| 256 | + mock_detect: MagicMock, |
| 257 | + mock_query_obj: MagicMock, |
| 258 | + mock_datasource: MagicMock, |
| 259 | +) -> None: |
| 260 | + """Detects when both top-level and column_config have AUTO.""" |
| 261 | + context = MagicMock() |
| 262 | + context.form_data = { |
| 263 | + "currency_format": {"symbol": "AUTO"}, |
| 264 | + "column_config": {"cost": {"currencyFormat": {"symbol": "AUTO"}}}, |
| 265 | + } |
| 266 | + mock_detect.return_value = "JPY" |
| 267 | + |
| 268 | + result = _detect_currency(context, mock_query_obj, mock_datasource) |
| 269 | + |
| 270 | + assert result == "JPY" |
| 271 | + mock_detect.assert_called_once() |
| 272 | + |
| 273 | + |
| 274 | +@patch("superset.common.query_actions.detect_currency") |
| 275 | +def test_detect_currency_top_level_auto_triggers_detection( |
| 276 | + mock_detect: MagicMock, |
| 277 | + mock_query_obj: MagicMock, |
| 278 | + mock_datasource: MagicMock, |
| 279 | +) -> None: |
| 280 | + """Detects when only top-level currency_format has AUTO.""" |
| 281 | + context = MagicMock() |
| 282 | + context.form_data = { |
| 283 | + "currency_format": {"symbol": "AUTO"}, |
| 284 | + "column_config": { |
| 285 | + "cost": {"currencyFormat": {"symbol": "USD"}} # explicit, not AUTO |
| 286 | + }, |
| 287 | + } |
| 288 | + mock_detect.return_value = "CAD" |
| 289 | + |
| 290 | + result = _detect_currency(context, mock_query_obj, mock_datasource) |
| 291 | + |
| 292 | + assert result == "CAD" |
| 293 | + mock_detect.assert_called_once() |
| 294 | + |
| 295 | + |
| 296 | +def test_detect_currency_column_config_no_currency_column_returns_none( |
| 297 | + mock_query_context_with_column_config: MagicMock, |
| 298 | + mock_query_obj: MagicMock, |
| 299 | +) -> None: |
| 300 | + """Returns None when column_config has AUTO but datasource lacks currency column.""" |
| 301 | + datasource = MagicMock() |
| 302 | + datasource.currency_code_column = None |
| 303 | + |
| 304 | + result = _detect_currency( |
| 305 | + mock_query_context_with_column_config, |
| 306 | + mock_query_obj, |
| 307 | + datasource, |
| 308 | + ) |
| 309 | + |
| 310 | + assert result is None |
| 311 | + |
| 312 | + |
| 313 | +@patch("superset.viz.detect_currency") |
| 314 | +def test_viz_detect_currency_checks_column_config_for_auto( |
| 315 | + mock_detect: MagicMock, |
| 316 | + base_query_obj: dict[str, Any], |
| 317 | + mock_datasource: MagicMock, |
| 318 | +) -> None: |
| 319 | + """Runs detection when column_config has AUTO currency (legacy API).""" |
| 320 | + mock_detect.return_value = "USD" |
| 321 | + form_data = {"column_config": {"cost": {"currencyFormat": {"symbol": "AUTO"}}}} |
| 322 | + viz = _build_viz(form_data, mock_datasource) |
| 323 | + |
| 324 | + result = BaseViz._detect_currency(viz, base_query_obj) |
| 325 | + |
| 326 | + assert result == "USD" |
| 327 | + mock_detect.assert_called_once_with( |
| 328 | + datasource=mock_datasource, |
| 329 | + filters=base_query_obj["filter"], |
| 330 | + granularity=base_query_obj["granularity"], |
| 331 | + from_dttm=base_query_obj["from_dttm"], |
| 332 | + to_dttm=base_query_obj["to_dttm"], |
| 333 | + extras=base_query_obj["extras"], |
| 334 | + ) |
| 335 | + |
| 336 | + |
| 337 | +@patch("superset.viz.detect_currency") |
| 338 | +def test_viz_detect_currency_skips_without_auto_in_column_config( |
| 339 | + mock_detect: MagicMock, |
| 340 | + base_query_obj: dict[str, Any], |
| 341 | + mock_datasource: MagicMock, |
| 342 | +) -> None: |
| 343 | + """Returns None when column_config has explicit currency (legacy API).""" |
| 344 | + form_data = {"column_config": {"cost": {"currencyFormat": {"symbol": "USD"}}}} |
| 345 | + viz = _build_viz(form_data, mock_datasource) |
| 346 | + |
| 347 | + result = BaseViz._detect_currency(viz, base_query_obj) |
| 348 | + |
| 349 | + assert result is None |
| 350 | + mock_detect.assert_not_called() |
0 commit comments