"""Integration tests for POST /identity/self/check/resources.""" from typing import Any, Dict import pytest import requests from mypy_boto3_dynamodb import DynamoDBClient from tests.integration import config from tests.integration.conftest import seed_test_pp_identity @pytest.mark.parametrize( "body, expected_resource_response, identity_tenant_type, seed_role", [ pytest.param( { "resources": [ { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 } }, }, "action": "view", } ] }, { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A resource with a tenant that the identity is an audience_development_analyst for. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], } }, }, "action": "view", } ] }, { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A resource with a tenant containing hierarchy that the identity is an audience_development_analyst for. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": ["not a uuid"], } }, }, "action": "view", } ] }, { "resource": { "resource_id": "890", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A resource with a tenant containing hierarchy that should not be considered valid. Identity has access to the tenant_uuid, so expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "456", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "327ea8cb-caef-43b5-ab60-9a425b3f344e", # noqa: E501 } }, }, "action": "view", } ] }, { "resource": { "resource_id": "456", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A resource with a tenant that that the identity does not have a role for. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123", "resource_type": "junk", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 } }, }, "action": "view", } ] }, { "resource": { "resource_id": "123", "resource_type": "junk", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A resource with a tenant that the identity is an audience_development_analyst for, but has a non-existent resource type. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123", "resource_type": "audience", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "21bb9c7f-6d47-4216-8442-b098f8c62e59", # noqa: E501 "tenant_hierarchy": [ "42879e8c-9f47-4214-b611-1e6feb0be6af" ], }, }, }, "action": "view", } ] }, { "resource": { "resource_id": "123", "resource_type": "audience", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "company_brand", "audience_development_analyst", id="""A resource with a tenant and hierarchy. The identity is an audience_development_analyst for a company_brand in the resource's tenant hierarchy. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], }, }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a tenant that the identity has access to. The identity is an audience_development_analyst. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "52879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], }, }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a tenant that the identity does not have access to. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "joint_venture": { "tenants": [ { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], "is_provider": True, "is_consumer": True, } ] } }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a joint venture with a tenant that the identity has access to.The identity is an audience_development_analyst. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "joint_venture": { "tenants": [ { "tenant_type": "account", "tenant_uuid": "242fa66d-193d-48b9-aaae-cefe7255b799", # noqa: E501 "tenant_hierarchy": [ "42879e8c-9f47-4214-b611-1e6feb0be6af" ], "is_provider": True, "is_consumer": True, } ] } }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a joint venture with a tenant in its hierarchy that the identity has access to. The identity is an audience_development_analyst. Expect allow.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "joint_venture": { "tenants": [ { "tenant_type": "account", "tenant_uuid": "242fa66d-193d-48b9-aaae-cefe7255b799", # noqa: E501 "tenant_hierarchy": [ # Randomly generated uuid "e5ea7ed7-3e98-4ddc-a5ca-909aaa74f5f7" ], "is_provider": True, "is_consumer": True, } ] } }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a joint venture with a tenant in its hierarchy that the identity does not have access to. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "joint_venture": { "tenants": [ { "tenant_type": "account", "tenant_uuid": "52879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], "is_provider": True, "is_consumer": True, } ] } }, }, "action": "view", } ] }, { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""A fan data list resource owned by a joint venture with a tenant that the identity does not have access to. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "integration test user's ad connection", "resource_type": "ad_connection", "attributes": { "identity_uuid": "4d5f24f5-83f9-4989-9f82-0924a5feaf88" }, }, "action": "connect", } ], }, { "resource": { "resource_id": "integration test user's ad connection", "resource_type": "ad_connection", "attributes": {}, }, "action": "connect", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""An ad_connection resource is owned by the integration test identity. However, the identity does not have the correct role. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "Maggie's ad connection", "resource_type": "ad_connection", "attributes": { "identity_uuid": "ca034907-1746-4c7b-9c42-502dc4002d6c", }, }, "action": "connect", } ], }, { "resource": { "resource_id": "Maggie's ad connection", "resource_type": "ad_connection", "attributes": {}, }, "action": "connect", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""An ad_connection resource is not owned by the integration test identity. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "A Signing Entity", "resource_type": "signing_entity", "attributes": {}, }, "action": "view", } ], }, { "resource": { "resource_id": "A Signing Entity", "resource_type": "signing_entity", "attributes": {}, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, "account", "audience_development_analyst", id="""An signing_entity resource is not viewable by audience_development_analyst. Expect deny.""", ), pytest.param( { "resources": [ { "resource": { "resource_id": "A Signing Entity", "resource_type": "signing_entity", "attributes": {}, }, "action": "view", } ], }, { "resource": { "resource_id": "A Signing Entity", "resource_type": "signing_entity", "attributes": {}, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, "account", "account_edit", id="""An signing_entity resource is viewable by account_edit role user. Expect allow.""", ), ], ) def test_check_my_resources( body: Dict[str, Any], expected_resource_response: Dict[str, Any], default_boto_client: DynamoDBClient, bearer_token_pdptest_user: str, bearer_token_pdptest_user_identity_uuid: str, identity_tenant_type: str, seed_role: str, ) -> None: """POST /identity/self/check/resources/ endpoint.""" url = f"{config.QA_BASE_URL}/identity/self/check/resources/" assert_check_my_resources( url, default_boto_client, bearer_token_pdptest_user, bearer_token_pdptest_user_identity_uuid, body=body, expected_resource_response=expected_resource_response, seed_role=seed_role, identity_tenant_type=identity_tenant_type, ) def assert_check_my_resources( url: str, boto_client: DynamoDBClient, bearer_token_pdptest_user: str, bearer_token_pdptest_user_identity_uuid: str, body: Dict[str, Any], expected_resource_response: Dict[str, Any], seed_role: str, identity_tenant_type: str = "account", ) -> None: """Verify response gives expected allow or deny dependent on principal's access.""" seed_test_pp_identity( boto_client, bearer_token_pdptest_user_identity_uuid, "42879e8c-9f47-4214-b611-1e6feb0be6af", seed_role, tenant_type=identity_tenant_type, ) response = requests.post( url, json=body, headers={"Authorization": f"Bearer {bearer_token_pdptest_user}"} ) assert expected_resource_response in response.json()["resources"], response.text def test_check_my_malformed_resource( default_boto_client: DynamoDBClient, bearer_token_pdptest_user: str, ) -> None: """Verify 200 with deny/validation error response for malformed resource.""" url = f"{config.QA_BASE_URL}/identity/self/check/resources/" # The Fan Data List resource schema expects a tenant OR joint venture, not both body = { "resources": [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "52879e8c-9f47-4214-b611-1e6feb0be6af", "tenant_hierarchy": [], }, "joint_venture": { "tenants": [ { "tenant_type": "account", "tenant_uuid": "52879e8c-9f47-4214-b611-1e6feb0be6af", # noqa: E501 "tenant_hierarchy": [], "is_provider": True, "is_consumer": True, } ] }, }, }, "action": "view", } ] } response = requests.post( url, json=body, headers={"Authorization": f"Bearer {bearer_token_pdptest_user}"} ) assert response.status_code == 200, f"Response: {response.text}" assert response.json()["resources"] == [ { "resource": { "resource_id": "123fandatalist", "resource_type": "fan_data_list", "attributes": {}, }, "action": "view", "effect": "deny", "errors": { "validation_errors": [ { "path": "/", "message": "valid against schemas at indexes 0 and 1", "source": "SOURCE_RESOURCE", } ] }, } ] def test_include_resource_attributes( default_boto_client: DynamoDBClient, bearer_token_pdptest_user: str, bearer_token_pdptest_user_identity_uuid: str, ) -> None: """Verify the endpoint returns resource attributes.""" body = { "resources": [ { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", } }, }, "action": "view", }, { "resource": { "resource_id": "this-tenant-has-hierarchy-to-fetch", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", } }, }, "action": "view", }, { "resource": { "resource_id": "preserve-hierarchy-even-if-wrong", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", "tenant_hierarchy": [ "fff741c2-6def-4493-bfdf-c2bcb1128e02" ], } }, }, "action": "view", }, { "resource": { "resource_id": "real-hierarchy-is-fetched", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", } }, }, "action": "view", }, { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "28b788a3-90f0-4416-9126-de19b084e9e4", "tenant_hierarchy": [], } }, }, "action": "view", }, ], "include_resource_attributes_in_response": True, } expected_resource_response = [ { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "42879e8c-9f47-4214-b611-1e6feb0be6af", } }, }, "action": "view", "effect": "allow", "errors": {"validation_errors": None}, }, { "resource": { "resource_id": "this-tenant-has-hierarchy-to-fetch", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", "tenant_hierarchy": [ "955a1bbd-b623-4ea1-ab5f-8d6620c442fb", "d25a4cd1-e820-45f2-be5c-56edcfeb8298", ], } }, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, { "resource": { "resource_id": "preserve-hierarchy-even-if-wrong", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", "tenant_hierarchy": ["fff741c2-6def-4493-bfdf-c2bcb1128e02"], } }, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, { "resource": { "resource_id": "real-hierarchy-is-fetched", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "fff741c2-6def-4493-bfdf-c2bcb1128e02", "tenant_hierarchy": [ "955a1bbd-b623-4ea1-ab5f-8d6620c442fb", "d25a4cd1-e820-45f2-be5c-56edcfeb8298", ], } }, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "28b788a3-90f0-4416-9126-de19b084e9e4", "tenant_hierarchy": [], } }, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, ] seed_test_pp_identity( default_boto_client, bearer_token_pdptest_user_identity_uuid, "42879e8c-9f47-4214-b611-1e6feb0be6af", "audience_development_analyst", tenant_type="account", ) response = requests.post( f"{config.QA_BASE_URL}/identity/self/check/resources/", json=body, headers={"Authorization": f"Bearer {bearer_token_pdptest_user}"}, ) assert expected_resource_response == response.json()["resources"] @pytest.mark.parametrize( "user_bearer_token", [ pytest.param( "bearer_token_pdptest_rap_admin_user", id="""An ad_connection resource is not owned by the integration test identity. Expect deny.""", ), pytest.param( "bearer_token_pdptest_not_rap_admin_user", id="Non RAP admin user can check own resources", ), ], ) def test_check_my_resources_rap_admin_or_not( user_bearer_token: str, default_boto_client: DynamoDBClient, request: pytest.FixtureRequest, ) -> None: """Verify RAP admin and non-RAP admin can check their own resources.""" body = { "resources": [ { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": { "tenant": { "tenant_type": "account", "tenant_uuid": "573d0372-7f2f-48a6-8deb-c9a6558f9549", } }, }, "action": "view", }, ], "include_resource_attributes_in_response": True, } bearer_token = request.getfixturevalue(user_bearer_token) response = requests.post( f"{config.QA_BASE_URL}/identity/self/check/resources/", json=body, headers={"Authorization": f"Bearer {bearer_token}"}, ) assert response.status_code == 200, f"Response: {response.text}" def test_uses_multi_tenant_hierarchy( default_boto_client: DynamoDBClient, bearer_token_pdptest_user: str, bearer_token_pdptest_user_identity_uuid: str, ) -> None: """Test multi tenant hierarchy is checked for RAP Admin permissions.""" # theorchard COMPANY_BRAND = "d25a4cd1-e820-45f2-be5c-56edcfeb8298" # Deathwish Records ACCOUNT_IN_CB = "4996d8ff-a64a-4d64-a725-4e0401b07425" # American Folk (belongs to Thirty Tigers) SUBACCOUNT_IN_CB = "df65ace8-5f20-464b-a03a-8713fe83dec1" # Wolfgang Amadeus Mozart (belongs to Test Label) LABEL_PARTICIPANT_IN_CB = "e055a30d-34de-450f-b1f2-1fa433ceb15a" # Human Re:Sources OTHER_CB = "ae886607-ae74-42f4-b7a7-d33f6a6288f4" # Bad Seed (belongs to awal) ACCOUNT_NOT_IN_CB = "42d022c2-50e4-4f5c-af30-f10dca6b8a50" # Columbia/ Billy Joel (belongs to sme) SUBACCOUNT_NOT_IN_CB = "6f6252f2-24de-40d9-8a93-927c420d4c7b" # Keith Burton (belongs to awal) LABEL_PARTICIPANT_NOT_IN_CB = "7d7fba9a-2a00-46cc-9b9a-0f5443cf025f" tenants = [ ("company_brand", COMPANY_BRAND, "deny"), ("account", ACCOUNT_IN_CB, "allow"), ("subaccount", SUBACCOUNT_IN_CB, "allow"), ("label_participant", LABEL_PARTICIPANT_IN_CB, "allow"), ("company_brand", OTHER_CB, "deny"), ("account", ACCOUNT_NOT_IN_CB, "deny"), ("subaccount", SUBACCOUNT_NOT_IN_CB, "deny"), ("label_participant", LABEL_PARTICIPANT_NOT_IN_CB, "deny"), ] # Seed Principal with the ows_permissions_rap_admin role seed_test_pp_identity( default_boto_client, bearer_token_pdptest_user_identity_uuid, COMPANY_BRAND, "ows_permissions_rap_admin", tenant_type="company_brand", ) action = "attach_and_detach_role" identity_uuid = "ca034907-1746-4c7b-9c42-502dc4002d6c" url = f"{config.QA_BASE_URL}/identity/self/check/resources/" body = { "resources": [ { "resource": { "resource_id": f"identity has a role with {tenant[1]}", "resource_type": "identity", "attributes": { "identity_uuid": identity_uuid, "tenant": { "tenant_type": tenant[0], "tenant_uuid": tenant[1], }, }, }, "action": action, } for tenant in tenants ] } response = requests.post( url, json=body, headers={"Authorization": f"Bearer {bearer_token_pdptest_user}"} ) actual = response.json()["resources"] actual_effects = [resource["effect"] for resource in actual] assert actual_effects == [tenant[2] for tenant in tenants] @pytest.mark.parametrize( "attributes, expected_attributes", [ pytest.param( { "id_to_uuid_exchange_tenant": { "tenant_type": "account", "tenant_id": 10, } }, { "id_to_uuid_exchange_tenant": { "tenant_type": "account", "tenant_id": 10, }, "tenant": { "tenant_type": "account", "tenant_uuid": "3391af7b-6596-4820-a21d-911f8b5f64b4", "tenant_hierarchy": [ "955a1bbd-b623-4ea1-ab5f-8d6620c442fb", "d25a4cd1-e820-45f2-be5c-56edcfeb8298", ], }, }, id="account is exchanged.", ), pytest.param( { "id_to_uuid_exchange_tenant": { "tenant_type": "subaccount", "tenant_id": 10, } }, { "id_to_uuid_exchange_tenant": { "tenant_type": "subaccount", "tenant_id": 10, }, "tenant": { "tenant_type": "subaccount", "tenant_uuid": "0778592c-c232-45af-bdce-af7fbbc1728e", "tenant_hierarchy": [ "955a1bbd-b623-4ea1-ab5f-8d6620c442fb", "d25a4cd1-e820-45f2-be5c-56edcfeb8298", "c14e18a9-2c9a-4d3d-acfc-e5897e202fd8", ], }, }, id="subaccount is exchanged.", ), pytest.param( { "id_to_uuid_exchange_tenant": { "tenant_type": "company_brand", "tenant_id": 10, } }, { "id_to_uuid_exchange_tenant": { "tenant_type": "company_brand", "tenant_id": 10, }, "tenant": { "tenant_type": "company_brand", "tenant_uuid": "2ed1077d-bcd3-4db6-9a47-c37da82aab18", "tenant_hierarchy": [ "955a1bbd-b623-4ea1-ab5f-8d6620c442fb", ], }, }, id="company_brand is exchanged.", ), ], ) def test_uses_update_with_id_to_uuid_exchange( attributes: dict[str, Any], expected_attributes: dict[str, Any], default_boto_client: DynamoDBClient, bearer_token_pdptest_user: str, bearer_token_pdptest_user_identity_uuid: str, ) -> None: """Verify the endpoint handles id_to_uuid_exchange_tenant in attribute.""" body = { "resources": [ { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": attributes, }, "action": "view", }, ], "include_resource_attributes_in_response": True, } expected_resource_response = [ { "resource": { "resource_id": "0", "resource_type": "fan_data_list", "attributes": expected_attributes, }, "action": "view", "effect": "deny", "errors": {"validation_errors": None}, }, ] seed_test_pp_identity( default_boto_client, bearer_token_pdptest_user_identity_uuid, "42879e8c-9f47-4214-b611-1e6feb0be6af", "audience_development_analyst", tenant_type="account", ) response = requests.post( f"{config.QA_BASE_URL}/identity/self/check/resources/", json=body, headers={"Authorization": f"Bearer {bearer_token_pdptest_user}"}, ) assert response.status_code == 200, response.text assert expected_resource_response == response.json()["resources"]