"""AliCloud International ``list_billing_daily`` NextToken pagination guards. Regression (#1655): ``DescribeInstanceBill`` pagination broke only when ``NextToken`` came back falsy. A degenerate BSS response returning a stable, non-empty ``NextToken`` would loop forever, hanging the worker. The fix adds a same-token termination guard plus a page-count ceiling that raise rather than spin. """ from __future__ import annotations from datetime import date from unittest.mock import AsyncMock, patch import pytest from app.integrations.driver_base import CloudScopeRef from app.integrations.drivers.alicloud_intl_cloud import AlicloudIntlCloudDriver class _StubIntlDriver(AlicloudIntlCloudDriver): """Bypass ``__init__`` so we can drive ``list_billing_daily`` directly.""" def __init__(self) -> None: self.integration_id = "test-alicloud-intl" # Pin FX so ``list_billing_daily`` never touches the DB / platform # settings for ``resolve_cny_per_usd``. self.config = {"cny_per_usd": 7.2} def _scope() -> CloudScopeRef: return CloudScopeRef(scope_type="aliyun_account", external_id="500001") @pytest.mark.asyncio async def test_list_billing_daily_raises_on_stable_next_token() -> None: drv = _StubIntlDriver() # Every page returns the SAME non-empty NextToken → infinite loop pre-fix. async def _fake_acs3(*_a, **_kw): return { "Data": { "Items": [], "NextToken": "STUCK-TOKEN", }, } with patch.object( AlicloudIntlCloudDriver, "_master_account_id", new=AsyncMock(return_value="500000"), ), patch.object( AlicloudIntlCloudDriver, "_acs3_call", side_effect=_fake_acs3, ), pytest.raises(RuntimeError, match="NextToken did not advance"): await drv.list_billing_daily( date(2026, 7, 1), date(2026, 7, 1), scope=_scope(), ) @pytest.mark.asyncio async def test_list_billing_daily_terminates_on_empty_next_token() -> None: """Sanity guard: the normal happy path (NextToken falsy on the last page) still terminates and returns a result.""" drv = _StubIntlDriver() pages = [ {"Data": {"Items": [], "NextToken": "page-2"}}, {"Data": {"Items": [], "NextToken": ""}}, # terminal ] calls = {"n": 0} async def _fake_acs3(*_a, **_kw): page = pages[calls["n"]] calls["n"] += 1 return page with patch.object( AlicloudIntlCloudDriver, "_master_account_id", new=AsyncMock(return_value="500000"), ), patch.object( AlicloudIntlCloudDriver, "_acs3_call", side_effect=_fake_acs3, ): out = await drv.list_billing_daily( date(2026, 7, 1), date(2026, 7, 1), scope=_scope(), ) assert calls["n"] == 2 assert len(out) == 1 # one CloudDailyCost row for the single day