"""Cloud-visibility whitelist — pure shape validation. Split from the integration tests so these stay DB-free (and so the module-level asyncio mark there doesn't apply to sync functions). """ from __future__ import annotations import uuid import pytest from app.api_clients.visibility import ( cloud_visibility_audit_detail, cloud_visibility_is_malformed, cloud_visibility_is_unrestricted, normalize_cloud_visibility, ) # ── Shape validation (pure) ─────────────────────────────────────────── def test_normalize_canonicalizes_and_dedupes(): sid = str(uuid.uuid4()) out = normalize_cloud_visibility({ "providers": ["AWS", "aws", " gcp "], "subscription_ids": [sid, sid], }) assert out == {"providers": ["aws", "gcp"], "subscription_ids": [sid]} def test_normalize_empty_collapses_to_unrestricted(): assert normalize_cloud_visibility({}) is None assert normalize_cloud_visibility({"providers": [], "subscription_ids": []}) is None assert normalize_cloud_visibility(None) is None def test_normalize_rejects_unknown_key(): # A typo'd key must NOT be ignored: {"provider": ["aws"]} would # otherwise normalize to "no constraints" and hand the client the # entire inventory — the exact failure this feature prevents. with pytest.raises(ValueError, match="unknown key"): normalize_cloud_visibility({"provider": ["aws"]}) def test_normalize_rejects_non_uuid_subscription(): with pytest.raises(ValueError, match="not a valid Helix subscription id"): normalize_cloud_visibility({"subscription_ids": ["123456789012"]}) def test_is_unrestricted_tolerates_legacy_shapes(): # Hand-edited / pre-normalization rows must not be mistaken for a # restriction — the read path asks this question before every query. assert cloud_visibility_is_unrestricted(None) is True assert cloud_visibility_is_unrestricted({}) is True assert cloud_visibility_is_unrestricted({"providers": []}) is True assert cloud_visibility_is_unrestricted({"providers": ["aws"]}) is False def test_audit_detail_summarizes_without_dumping_uuids(): ids = [str(uuid.uuid4()) for _ in range(3)] detail = cloud_visibility_audit_detail( {"providers": ["aws"], "subscription_ids": ids}, ) assert detail == { "cloud_visibility": "restricted", "providers": ["aws"], "subscription_count": 3, } assert cloud_visibility_audit_detail(None) == {"cloud_visibility": "unrestricted"} def test_malformed_is_distinguished_from_unrestricted(): """``cloud_visibility_is_unrestricted`` answers True for a non-dict — deliberately, so callers may ask it of a legacy row without re-normalizing. That tolerance is only safe while a SECOND predicate separates "no whitelist stored" from "a whitelist nobody can read": the first grants everything, the second must grant nothing.""" assert cloud_visibility_is_malformed(None) is False assert cloud_visibility_is_malformed({}) is False assert cloud_visibility_is_malformed({"providers": ["aws"]}) is False for broken in ("not-an-object", ["aws"], 7, True): assert cloud_visibility_is_malformed(broken) is True, broken # The tolerant predicate still says "unrestricted" about these — # which is exactly why callers must consult the malformed one # first. assert cloud_visibility_is_unrestricted(broken) is True def test_audit_detail_calls_an_unreadable_whitelist_what_it_is(): """Clearing an unreadable whitelist takes the client from *reads nothing* to *reads the whole cloud inventory* — the single largest widening on this surface. Summarizing the old value as "unrestricted" would write ``from == to`` and hide it completely.""" broken = cloud_visibility_audit_detail("not-an-object") assert broken["cloud_visibility"] == "misconfigured" assert broken["stored_type"] == "str" assert broken != cloud_visibility_audit_detail(None) assert cloud_visibility_audit_detail(["aws"])["stored_type"] == "list"