"""Tests for native Aliyun + AliCloud Intl monthly billing via BSS. Validates: * BSS host is correct per tenant region (CN vs Intl) * ``QueryAccountBill`` paginates via ``NextToken`` * Service-level breakdown is summed across pages * Currency is reported as CNY (Aliyun's native unit) """ from __future__ import annotations from datetime import date from unittest.mock import patch import pytest from app.domains.cmdb.providers.alicloud_intl import AlicloudIntlProvider from app.domains.cmdb.providers.aliyun import AliyunProvider from app.domains.cmdb.providers.base import ProviderCredentials def _creds(provider_type: str = "aliyun") -> ProviderCredentials: return ProviderCredentials( provider_id="test", provider_type=provider_type, account_alias="test", fields={ "access_key_id": "AK", "access_key_secret": "SK", }, ) @pytest.mark.asyncio async def test_get_monthly_billing_uses_cn_bss_host_and_returns_cny(): """国内 Aliyun: BSS endpoint must be ``business.aliyuncs.com``. Returned currency must be CNY (no silent USD conversion).""" p = AliyunProvider(_creds("aliyun")) captured: list[tuple[str, dict]] = [] async def _fake_call(endpoint, params): captured.append((endpoint, dict(params))) return { "Data": { "Items": {"Item": [ {"ProductName": "ECS", "PretaxAmount": 100.5}, {"ProductName": "OSS", "PretaxAmount": 25.25}, ]}, "NextToken": None, } } with patch.object(p, "_call", new=_fake_call): out = await p.get_monthly_billing(date(2026, 3, 15)) await p.close() assert len(captured) == 1 endpoint, params = captured[0] assert endpoint == "https://business.aliyuncs.com/" assert params["Action"] == "QueryAccountBill" assert params["BillingCycle"] == "2026-03" assert params["Granularity"] == "MONTHLY" assert params["Version"] == "2017-12-14" assert out["month"] == date(2026, 3, 1) assert out["currency"] == "CNY" # Round-666 fix: ``total_cost_usd`` now carries the NATIVE CNY value # (== ``total_cost_native``) — the driver no longer pre-multiplies by # a stale hard-coded 0.14. The billing handler resolves CNY→USD with # the dynamic platform rate (Tencent/UCloud contract); on FX-fail it # keeps ``currency='CNY'`` so value + label agree and the overview # reader converts exactly once. ``cost_by_service`` keeps native units. assert out["total_cost_native"] == pytest.approx(125.75) assert out["total_cost_usd"] == pytest.approx(125.75) assert out["cost_by_service"]["ECS"] == pytest.approx(100.5) assert out["cost_by_service"]["OSS"] == pytest.approx(25.25) @pytest.mark.asyncio async def test_alicloud_intl_uses_sg_bss_host(): """海外 AliCloud: BSS endpoint MUST be the Singapore variant — using the CN host returns ``NotApplicable``. Same fix family as PR #49 for daily billing.""" p = AlicloudIntlProvider(_creds("alicloud_intl")) captured: list[tuple[str, dict]] = [] async def _fake_call(endpoint, params): captured.append((endpoint, dict(params))) return {"Data": {"Items": {"Item": []}, "NextToken": None}} with patch.object(p, "_call", new=_fake_call): await p.get_monthly_billing(date(2026, 3, 1)) await p.close() endpoint, _ = captured[0] assert endpoint == "https://business.ap-southeast-1.aliyuncs.com/" @pytest.mark.asyncio async def test_get_monthly_billing_follows_next_token(): """A real busy month spans multiple BSS pages; the implementation must follow ``NextToken`` until the cursor disappears, otherwise silent under-counting happens.""" p = AliyunProvider(_creds("aliyun")) pages = [ {"Data": {"Items": {"Item": [ {"ProductName": "ECS", "PretaxAmount": 50.0}, ]}, "NextToken": "page2"}}, {"Data": {"Items": {"Item": [ {"ProductName": "ECS", "PretaxAmount": 25.0}, {"ProductName": "RDS", "PretaxAmount": 10.0}, ]}, "NextToken": None}}, ] page_iter = iter(pages) async def _fake_call(endpoint, params): return next(page_iter) with patch.object(p, "_call", new=_fake_call): out = await p.get_monthly_billing(date(2026, 3, 1)) await p.close() # ECS summed across both pages — values are CNY natively. Round-666: # ``total_cost_usd`` carries the native CNY value (handler resolves FX). assert out["cost_by_service"]["ECS"] == pytest.approx(75.0) assert out["cost_by_service"]["RDS"] == pytest.approx(10.0) assert out["total_cost_native"] == pytest.approx(85.0) assert out["total_cost_usd"] == pytest.approx(85.0) @pytest.mark.asyncio async def test_default_month_is_previous_full_month(): """When ``month`` is omitted, the handler defaults to the previous full month — matches AWS impl + the cron schedule (runs day 2 of every month for last month's data).""" p = AliyunProvider(_creds("aliyun")) async def _fake_call(endpoint, params): # Echo the cycle back so we can assert on it return {"Data": {"Items": {"Item": []}, "NextToken": None}, "_cycle": params["BillingCycle"]} with patch.object(p, "_call", new=_fake_call): out = await p.get_monthly_billing() await p.close() today = date.today() expected_first = (today.replace(day=1) - __import__("datetime").timedelta(days=1)).replace(day=1) assert out["month"] == expected_first