"""Schema tests.""" from typing import Any, Dict, Optional from uuid import UUID import pytest from pydantic import ValidationError from pdp.connectors.cerbos_policy_parser import ( PolicyMetadataDatabase, ResourcePolicyMetadata, ) from pdp.constants.constants import TenantType from pdp.fastapi.schemas.identity import ( DEFAULT_PAGINATION_CURSOR, CheckResourceAction, CheckResourceActionResult, IdentityTenant, ImmutablePaginationCursor, OwsError, PaginationCursor, Resource, Role, RolesResponse, TenantRoles, ) from pdp.fastapi.schemas.tenant import ( IdToUuidExchangeTenant, Tenant, TenantWithMaybeHierarchy, UuidToIdExchangeTenant, ) @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, False, {"cursor": None, "shorthand": None}), ({"cursor": "hello"}, False, {"cursor": "hello", "shorthand": None}), ({"shorthand": "hello"}, False, {"cursor": None, "shorthand": "hello"}), ( {"cursor": "what-up", "shorthand": "hello"}, False, {"cursor": "what-up", "shorthand": "hello"}, ), ], ) def test_pagination_cursor_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of PaginationCursor schema.""" if expect_exception: with pytest.raises(Exception): PaginationCursor.model_validate(payload) else: actual = PaginationCursor.model_validate(payload) assert actual == PaginationCursor(**expected) @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ( { "role": " ", }, True, None, ), ( { " role ": "a role", }, False, { "role": "a role", }, ), ( { "content_types": ["physical", "digital"], }, True, None, ), ( { "role": "a role", }, False, { "role": "a role", }, ), ( { "role": "a role", "content_types": ["physical", "digital"], }, False, { "role": "a role", "content_types": ["physical", "digital"], }, ), ( { "role": " a role ", "something ": " else ", " content_types": ["physical ", "digital"], }, False, { "role": "a role", "something": "else", "content_types": [ "physical ", # root_validator does not handle str w/in a list. "digital", ], }, ), ], ) def test_role_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of Role schema.""" if expect_exception: with pytest.raises(Exception): Role.model_validate(payload) else: actual = Role.model_validate(payload) assert actual == Role(**expected) @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ( { "tenant_type": "account", }, True, None, ), ( { "tenant_type": "account", "tenant_uuid": "123-456", }, True, None, ), ( { "tenant_type": "account", "tenant_uuid": "123-456", "roles": [], }, True, None, ), ( { "tenant_type": "vendor", "tenant_uuid": "123-456", "roles": [], }, True, None, ), ( { "tenant_type": "account", "tenant_uuid": "123-456", "version": "13", }, True, None, ), ( { "tenant_type": "account", "tenant_uuid": " 89b79b52-de18-4644-bd2b-bd28fdbeebaa ", "version": "13", "roles": [], }, True, None, ), ( { "tenant_type": "account", "tenant_uuid": "8a2357f0-417a-4041-95b8-620156272545", "roles": [], }, False, { "tenant_type": "account", "tenant_uuid": UUID("8a2357f0-417a-4041-95b8-620156272545"), "roles": [], }, ), ( { "tenant_type": "account", "tenant_uuid": "89b79b52-de18-4644-bd2b-bd28fdbeebaa", "version": "13", "roles": [], }, False, { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), "roles": [], }, ), ], ) def test_tenant_roles_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of TenantRoles schema.""" if expect_exception: with pytest.raises(Exception): TenantRoles.model_validate(payload) else: actual = TenantRoles.model_validate(payload) assert actual == TenantRoles(**expected) @pytest.mark.parametrize( "left, right, expected", [ pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[], ), True, id="TenantRoles with empty roles are equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="cat")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog")], ), False, id="TenantRoles with different roles are not equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="cat")], ), False, id="Left/right usage of comparator is equivalent", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog")], ), True, id="TenantRoles with same list of roles equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog"), Role(role="cat")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="cat"), Role(role="dog")], ), False, id="TenantRoles with differently ordered list are not equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_SUBACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog"), Role(role="cat")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="cat"), Role(role="dog")], ), False, id="TenantRoles with different TenantType are not equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog"), Role(role="cat")], ), TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), roles=[Role(role="dog"), Role(role="cat")], ), False, id="TenantRoles with different uuid are not equal", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog"), Role(role="cat")], ), IdentityTenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), roles=[Role(role="dog"), Role(role="cat")], identity_uuid="20212b39-90b3-4486-9ce7-a1323529bc4d", ), False, id="IdentityTenant is not equal, even though IdentityTenant inherits TenantRoles", # noqa: E501 ), ], ) def test_tenant_roles__eq__( left: TenantRoles, right: Any, expected: bool, ) -> None: """Test == operator""" actual = left == right assert actual == expected @pytest.mark.parametrize( "other", [ pytest.param("a string"), pytest.param( Tenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), ), ), pytest.param( UuidToIdExchangeTenant( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), ), ), ], ) def test_tenant_roles__eq__raises(other: Any) -> None: """Test == operator raises when something is not a TenantRole.""" with pytest.raises(AssertionError): TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), roles=[Role(role="cat"), Role(role="dog")], ) == other @pytest.mark.parametrize( "tenant_roles, expected", [ pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), roles=[Role(role="cat"), Role(role="dog")], ), "TenantType.TENANT_TYPE_ACCOUNT#8a2357f0-417a-4041-95b8-620156272545[Role(role='cat'), Role(role='dog')]", # noqa: E501 id="Roles are part of the representation", ), pytest.param( TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("8a2357f0-417a-4041-95b8-620156272545"), roles=[], ), "TenantType.TENANT_TYPE_ACCOUNT#8a2357f0-417a-4041-95b8-620156272545[]", id="Empty roles are part of the representation", ), ], ) def test_tenant_roles__repr__( tenant_roles: TenantRoles, expected: str, ) -> None: actual = repr(tenant_roles) assert actual == expected def test_tenant_roles__hash__() -> None: """Test the TenantRoles __hash__ method.""" tenant_roles_01 = TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("2a120908-d88d-49fc-84b3-eb1598893256"), roles=[], ) tenant_roles_02 = TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("a493eeb8-2b62-457e-9a65-a0ad0fb2f826"), roles=[], ) tenant_roles_03 = TenantRoles( tenant_type=TenantType.TENANT_TYPE_ACCOUNT, tenant_uuid=UUID("d447c5df-223f-46e8-be7f-9275e1c14202"), roles=[], ) tenants = [ tenant_roles_03, tenant_roles_03, tenant_roles_01, tenant_roles_01, tenant_roles_02, ] assert sorted(set(tenants)) == [ tenant_roles_01, tenant_roles_02, tenant_roles_03, ] def test_tenant_roles_as_cerbos_principal_tenants_attribute() -> None: """Test TenantRoles.as_cerbos_principal_tenants_attribute helper.""" actual = TenantRoles( tenant_type="company_brand", tenant_uuid="89b79b52-de18-4644-bd2b-bd28fdbeebaa", roles=[ Role(role="dog_whisperer"), Role(role="cat_scratcher", parts=["belly", "noggin"]), ], ).as_cerbos_principal_tenants_attribute() assert actual == { "tenant_type": "company_brand", "tenant_uuid": "89b79b52-de18-4644-bd2b-bd28fdbeebaa", "roles": { "dog_whisperer": {"role": "dog_whisperer"}, "cat_scratcher": {"role": "cat_scratcher", "parts": ["belly", "noggin"]}, }, } @pytest.mark.parametrize( "payload, expect_exception, expected", [ ( {"cursor": {}, "tenants": {123}}, True, None, ), ( {}, False, { "cursor": DEFAULT_PAGINATION_CURSOR, "errors": {}, "tenants": {}, }, ), ( {"cursor": {}}, False, { "cursor": {"cursor": None, "shorthand": None}, "errors": {}, "tenants": {}, }, ), ( {"cursor": {}, "errors": {"123": 456}}, False, { "cursor": {"cursor": None, "shorthand": None}, "errors": {"123": 456}, "tenants": {}, }, ), ( { "cursor": {}, "tenants": { "123-456": { "tenant_type": "account", "tenant_uuid": "123-456", "version": "13", "roles": [], } }, }, True, { "cursor": {"cursor": None, "shorthand": None}, "errors": {}, "tenants": { "ff081af8-b20d-47ea-a59f-821f95b89370": { "tenant_type": "account", "tenant_uuid": "ff081af8-b20d-47ea-a59f-821f95b89370", "roles": [], } }, }, ), ( { "cursor": {}, "tenants": { "ff081af8-b20d-47ea-a59f-821f95b89370": { "tenant_type": "account", "tenant_uuid": "ff081af8-b20d-47ea-a59f-821f95b89370", "version": "13", "roles": [], } }, }, False, { "cursor": {"cursor": None, "shorthand": None}, "errors": {}, "tenants": { UUID("ff081af8-b20d-47ea-a59f-821f95b89370"): { "tenant_type": "account", "tenant_uuid": UUID("ff081af8-b20d-47ea-a59f-821f95b89370"), "roles": [], } }, }, ), ], ) def test_roles_response_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of RolesResponse schema.""" if expect_exception: with pytest.raises(Exception): RolesResponse.model_validate(payload) else: actual = RolesResponse.model_validate(payload) assert actual == RolesResponse(**expected) @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ({"resource_id": "123"}, True, None), ({"resource_type": "what are you"}, True, None), ( {"resource_type": "what are you", "resource_id": "123"}, False, {"resource_id": "123", "resource_type": "what are you", "attributes": {}}, ), ({"attributes": {"look": "in cerbos schema"}}, True, None), ( { "resource_id": 123, "resource_type": "", "attributes": {"attribute": "1"}, }, True, None, ), ( { "resource_id": "", "resource_type": "audience", "attributes": {"attribute": "1"}, }, True, None, ), ( { "resource_id": 123, "resource_type": "identity", "attributes": {"attribute": "1"}, }, False, { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, ), ], ) def test_resource_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of Resource schema.""" if expect_exception: with pytest.raises(Exception): Resource.model_validate(payload) else: actual = Resource.model_validate(payload) assert actual == Resource(**expected) @pytest.mark.parametrize( "resource, expected", [ pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, ), None, id="No tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": "1"}, }, ), None, id="Invalid tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": {"tenant_uuid": "123"}}, }, ), None, id="Invalid tenant uuid in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "not_a_type", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } }, } ), None, id="Invalid tenant type in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } }, }, ), Tenant.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } ), id="Valid tenant in attributes", ), ], ) def test_resource_get_tenant(resource: Resource, expected: Optional[Tenant]) -> None: """Test Resource get_tenant.""" tenant = resource.get_tenant() assert tenant == expected @pytest.mark.parametrize( "resource, expected", [ pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, ), None, id="No tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": "1"}, }, ), None, id="Invalid tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": {"tenant_uuid": "123"}}, }, ), None, id="Invalid tenant uuid in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "not_a_type", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } }, } ), None, id="Invalid tenant type in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } }, }, ), TenantWithMaybeHierarchy.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } ), id="Valid tenant in attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), "tenant_hierarchy": [ UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), ], } }, }, ), TenantWithMaybeHierarchy.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), "tenant_hierarchy": [UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa")], } ), id="Valid tenant with hierarchy in attributes", ), ], ) def test_resource_get_tenant_with_maybe_hierarchy( resource: Resource, expected: Optional[TenantWithMaybeHierarchy], ) -> None: """Test Resource get_tenant_with_maybe_hierarchy.""" tenant = resource.get_tenant_with_maybe_hierarchy() assert tenant == expected @pytest.mark.parametrize( "resource, expected", [ pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, ), None, id="No id_to_uuid_exchange_tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": {"id_to_uuid_exchange_tenant": "1"}, }, ), None, id="Invalid id_to_uuid_exchange_tenant provided in resource attributes", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "id_to_uuid_exchange_tenant": { "tenant_type": "not_a_type", "tenant_uuid": 123, } }, } ), None, id="Invalid tenant type for id_to_uuid_exchange_tenant", ), pytest.param( Resource.model_validate( { "resource_id": "123", "resource_type": "identity", "attributes": { "id_to_uuid_exchange_tenant": { "tenant_type": "account", "tenant_id": "z34ae", } }, }, ), IdToUuidExchangeTenant.model_validate( { "tenant_type": "account", "tenant_id": "z34ae", } ), id="Valid id_to_uuid_exchange_tenant in attributes", ), ], ) def test_resource_get_id_to_uuid_exchange_tenant( resource: Resource, expected: Optional[IdToUuidExchangeTenant], ) -> None: """Test Resource get_id_to_uuid_exchange_tenant.""" tenant = resource.get_id_to_uuid_exchange_tenant() assert tenant == expected @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ({"action": "view"}, True, None), ( { "resource": { "resource_id": 123, "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "", }, True, None, ), ( { "resource": { "resource_id": 123, "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "view", }, False, { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "view", }, ), ], ) def test_check_resource_action_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of CheckResourceAction schema.""" if expect_exception: with pytest.raises(Exception): CheckResourceAction.model_validate(payload) else: actual = CheckResourceAction.model_validate(payload) assert actual == CheckResourceAction(**expected) @pytest.mark.parametrize( "cra, expected", [ pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "view", } ), None, id="No tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": "1"}, }, "action": "view", } ), None, id="Invalid tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": {"tenant_uuid": "123"}}, }, "action": "view", } ), None, id="Invalid tenant uuid provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "not_a_type", "tenant_uuid": UUID( "89b79b52-de18-4644-bd2b-bd28fdbeebaa" ), } }, }, "action": "view", } ), None, id="Invalid tenant type provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID( "89b79b52-de18-4644-bd2b-bd28fdbeebaa" ), } }, }, "action": "view", } ), Tenant.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } ), id="Valid tenant provided on CheckResourceAction", ), ], ) def test_check_resource_action_get_tenant( cra: CheckResourceAction, expected: Optional[Tenant] ) -> None: """Test CheckResourceAction get_tenant.""" tenant = cra.get_tenant() assert tenant == expected @pytest.mark.parametrize( "cra, expected", [ pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "view", } ), None, id="No tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": "1"}, }, "action": "view", } ), None, id="Invalid tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"tenant": {"tenant_uuid": "123"}}, }, "action": "view", } ), None, id="Invalid tenant uuid provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "not_a_type", "tenant_uuid": UUID( "89b79b52-de18-4644-bd2b-bd28fdbeebaa" ), } }, }, "action": "view", } ), None, id="Invalid tenant type provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID( "89b79b52-de18-4644-bd2b-bd28fdbeebaa" ), } }, }, "action": "view", } ), TenantWithMaybeHierarchy.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), } ), id="Valid tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": UUID( "89b79b52-de18-4644-bd2b-bd28fdbeebaa" ), "tenant_hierarchy": [ "89b79b52-de18-4644-bd2b-bd28fdbeebaa", ], } }, }, "action": "view", } ), TenantWithMaybeHierarchy.model_validate( { "tenant_type": "account", "tenant_uuid": UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa"), "tenant_hierarchy": [UUID("89b79b52-de18-4644-bd2b-bd28fdbeebaa")], } ), id="Valid tenant hierarchy provided on CheckResourceAction", ), ], ) def test_check_resource_action_get_tenant_with_maybe_hierarchy( cra: CheckResourceAction, expected: Optional[TenantWithMaybeHierarchy] ) -> None: """Test CheckResourceAction get_tenant_with_maybe_hierarchy.""" tenant = cra.get_tenant_with_maybe_hierarchy() assert tenant == expected @pytest.mark.parametrize( "cra, expected", [ pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"attribute": "1"}, }, "action": "view", } ), None, id="No IdToUuidExchangeTenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {"id_to_uuid_exchange_tenant": "1"}, }, "action": "view", } ), None, id="Invalid id_to_uuid_exchange_tenant provided on CheckResourceAction", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "id_to_uuid_exchange_tenant": {"tenant_id": "123"} }, }, "action": "view", } ), None, id="Tenant type needs to be provided for id_to_uuid_exchange_tenant", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "id_to_uuid_exchange_tenant": { "tenant_type": "not_a_type", "tenant_id": "123", } }, }, "action": "view", } ), None, id="Invalid tenant type provided for id_to_uuid_exchange_tenant", ), pytest.param( CheckResourceAction.model_validate( { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": { "id_to_uuid_exchange_tenant": { "tenant_type": "account", "tenant_id": 123, } }, }, "action": "view", } ), IdToUuidExchangeTenant.model_validate( { "tenant_type": "account", "tenant_id": 123, } ), id="Valid tenant provided on CheckResourceAction", ), ], ) def test_check_resource_action_get_id_to_uuid_exchange_tenant( cra: CheckResourceAction, expected: Optional[IdToUuidExchangeTenant] ) -> None: """Test CheckResourceAction get_id_to_uuid_exchange_tenant.""" tenant = cra.get_id_to_uuid_exchange_tenant() assert tenant == expected @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ( { "resource": { "resource_id": 123, "resource_type": "identity", }, "action": "view", "effect": "not-supported", }, True, None, ), ( { "resource": { "resource_id": 1.234, "resource_type": "identity", }, "action": "view", "effect": "allow", }, True, None, ), ( { "resource": { "resource_id": None, "resource_type": "identity", }, "action": "view", "effect": "allow", }, True, None, ), ( { "resource": { "resource_id": False, "resource_type": "identity", }, "action": "view", "effect": "allow", }, False, { "resource": { "resource_id": "0", "resource_type": "identity", }, "action": "view", "effect": "allow", }, ), ( { "resource": { "resource_id": 123, "resource_type": "identity", }, "action": "", "effect": "deny", "errors": {"validation_errors": ["error1", "error2"]}, }, True, None, ), ( { "resource": { "resource_id": 123, "resource_type": "identity", }, "action": "view", "effect": "allow", }, False, { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {}, }, ), ( { "resource": { "resource_id": 123, "resource_type": "identity", }, "action": "view", "effect": "deny", "errors": {"validation_errors": ["error1", "error2"]}, }, False, { "resource": { "resource_id": "123", "resource_type": "identity", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": ["error1", "error2"]}, }, ), ], ) def test_check_resource_action_result_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of CheckResourceActionResult schema.""" if expect_exception: with pytest.raises(Exception): CheckResourceActionResult.model_validate(payload) else: actual = CheckResourceActionResult.model_validate(payload) assert actual == CheckResourceActionResult(**expected) @pytest.mark.parametrize( "payload, expect_exception, expected", [ ({}, True, None), ({}, True, None), ( {"message": "whats up"}, True, None, ), ( {"code": "hello"}, True, None, ), ( {"code": "hello", "message": "whats up"}, False, {"code": "hello", "message": "whats up"}, ), ], ) def test_ows_error_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of OwsError schema.""" if expect_exception: with pytest.raises(Exception): OwsError.model_validate(payload) else: actual = OwsError.model_validate(payload) assert actual == OwsError(**expected) @pytest.mark.parametrize( "payload, expect_exception, expected", [ pytest.param({}, True, None, id="Fields must be present"), pytest.param( { "tenant_type": "account", }, True, None, id="Tenant must be fully represented", ), pytest.param( { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": "9b36a627-4be9-4762-b790-5628676f5f90", "roles": [], "version": "1.23", }, True, None, id="Version must be an integer, no decimals", ), pytest.param( { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": "9b36a627-4be9-4762-b790-5628676f5f90", "roles": [], "version": "not an integer", }, True, None, id="Version must be an integer", ), pytest.param( { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": "9b36a627-4be9-4762-b790-5628676f5f90", "roles": [], }, False, { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": UUID("9b36a627-4be9-4762-b790-5628676f5f90"), "roles": [], "version": "0", "updated_at": None, "updated_by": None, "updated_impersonated_by": None, "created_at": None, "created_by": None, "created_impersonated_by": None, }, id="Bare minimum fields", ), pytest.param( { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": "9b36a627-4be9-4762-b790-5628676f5f90", "roles": [], "updated_at": "today", "updated_by": "de691237-89c4-4e57-8b86-c970ba18fa25", "created_at": "yesterday", "created_by": "67d2eb11-fa85-4b31-8859-7b3850ab9ba3", }, False, { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": UUID("9b36a627-4be9-4762-b790-5628676f5f90"), "roles": [], "version": "0", "updated_at": "today", "updated_by": "de691237-89c4-4e57-8b86-c970ba18fa25", "updated_impersonated_by": None, "created_at": "yesterday", "created_by": "67d2eb11-fa85-4b31-8859-7b3850ab9ba3", "created_impersonated_by": None, }, id="Standard audit fields are set.", ), pytest.param( { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": "9b36a627-4be9-4762-b790-5628676f5f90", "roles": [], "updated_at": "today", "updated_by": "de691237-89c4-4e57-8b86-c970ba18fa25", "created_at": "yesterday", "created_by": "67d2eb11-fa85-4b31-8859-7b3850ab9ba3", "updated_impersonated_by": "269395a5-9462-4526-b8cc-8a573a4537d4", "created_impersonated_by": "135198b7-4935-4c82-b464-d6f6d17d1ecc", }, False, { "identity_uuid": "an identity_uuid", "tenant_type": "company_brand", "tenant_uuid": UUID("9b36a627-4be9-4762-b790-5628676f5f90"), "roles": [], "version": "0", "updated_at": "today", "updated_by": "de691237-89c4-4e57-8b86-c970ba18fa25", "updated_impersonated_by": "269395a5-9462-4526-b8cc-8a573a4537d4", "created_at": "yesterday", "created_by": "67d2eb11-fa85-4b31-8859-7b3850ab9ba3", "created_impersonated_by": "135198b7-4935-4c82-b464-d6f6d17d1ecc", }, id="Impersonated_by audit fields are set.", ), ], ) def test_identity_tenant_model_validate( payload: Any, expect_exception: bool, expected: Dict[str, Any] ) -> None: """Parametrized testing of IdentityTenant schema.""" if expect_exception: with pytest.raises(Exception): IdentityTenant.model_validate(payload) else: actual = IdentityTenant.model_validate(payload) assert actual == IdentityTenant(**expected) def test_identity_tenant_increment_version( identity_uuid: str, tenant_1_uuid: UUID ) -> None: """Test IdentityTenant.increment_version.""" tenant_state = IdentityTenant( **{ "identity_uuid": str(identity_uuid), "tenant_type": "company_brand", "tenant_uuid": tenant_1_uuid, "roles": [], } ) assert tenant_state.version == "0" tenant_state.increment_version() assert tenant_state.version == "1" tenant_state.version = "43" tenant_state.increment_version() assert tenant_state.version == "44" def test_immutable_cursor() -> None: """Verify DEFAULT_PAGINATION_CURSOR cannot be updated accidentally by code.""" cursor = ImmutablePaginationCursor(cursor=None, shorthand=None) with pytest.raises(ValidationError): cursor.cursor = "This should fail!" # type: ignore with pytest.raises(ValidationError): DEFAULT_PAGINATION_CURSOR.cursor = "This should fail" # type: ignore assert DEFAULT_PAGINATION_CURSOR.cursor is None assert DEFAULT_PAGINATION_CURSOR.shorthand is None # --- Resource.needs_account_feature_controls_lookup --- @pytest.mark.parametrize( "resource_type, expected", [ pytest.param("digital_audio", True, id="requires_feature_controls"), pytest.param("nonexistent", False, id="unknown_resource_type_returns_false"), ], ) def test_resource_needs_account_feature_controls_lookup( resource_type: str, expected: bool ) -> None: db = PolicyMetadataDatabase() db.add_policy( ResourcePolicyMetadata( resource_type="digital_audio", requires_account_feature_controls=True, ) ) resource = Resource(resource_id="123", resource_type=resource_type) assert resource.needs_account_feature_controls_lookup(db) is expected