from unittest.mock import call from uuid import uuid4 import pytest from app import invite_api_client, service_api_client, user_api_client from app.notify_client.service_api_client import ServiceAPIClient from tests.conftest import SERVICE_ONE_ID FAKE_TEMPLATE_ID = uuid4() def test_client_posts_archived_true_when_deleting_template(mocker): mocker.patch("app.notify_client.current_user", id="1") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" ) expected_data = {"archived": True, "created_by": "1"} expected_url = "/service/{}/template/{}".format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID) client = ServiceAPIClient() mock_post = mocker.patch( "app.notify_client.service_api_client.ServiceAPIClient.post" ) mocker.patch( "app.notify_client.service_api_client.ServiceAPIClient.get", return_value={"data": {"id": str(FAKE_TEMPLATE_ID)}}, ) client.delete_service_template(SERVICE_ONE_ID, FAKE_TEMPLATE_ID) mock_post.assert_called_once_with(expected_url, data=expected_data) assert ( call(f"service-{SERVICE_ONE_ID}-template-*") in mock_redis_delete_by_pattern.call_args_list ) def test_client_gets_service(mocker): client = ServiceAPIClient() mock_get = mocker.patch.object(client, "get", return_value={}) client.get_service("foo") mock_get.assert_called_once_with("/service/foo") @pytest.mark.parametrize("limit_days", [None, 30]) def test_client_gets_service_statistics(mocker, limit_days): client = ServiceAPIClient() mock_get = mocker.patch.object(client, "get", return_value={"data": {"a": "b"}}) ret = client.get_service_statistics("foo", limit_days) assert ret == {"a": "b"} mock_get.assert_called_once_with( "/service/foo/statistics", params={"limit_days": limit_days} ) def test_client_only_updates_allowed_attributes(mocker): mocker.patch("app.notify_client.current_user", id="1") with pytest.raises(TypeError) as error: ServiceAPIClient().update_service("service_id", foo="bar") assert str(error.value) == "Not allowed to update service attributes: foo" def test_client_creates_service_with_correct_data( mocker, active_user_with_permissions, fake_uuid, ): client = ServiceAPIClient() mock_post = mocker.patch.object(client, "post", return_value={"data": {"id": None}}) mocker.patch("app.notify_client.current_user", id="123") client.create_service( "My first service", "central_government", 1, True, fake_uuid, "test@example.com", ) mock_post.assert_called_once_with( "/service", dict( # Autogenerated arguments created_by="123", active=True, # ‘service_name’ argument is coerced to ‘name’ name="My first service", # The rest pass through with the same names organization_type="central_government", message_limit=1, restricted=True, user_id=fake_uuid, email_from="test@example.com", ), ) def test_get_precompiled_template(mocker): client = ServiceAPIClient() mock_get = mocker.patch.object(client, "get") client.get_precompiled_template(SERVICE_ONE_ID) mock_get.assert_called_once_with( "/service/{}/template/precompiled".format(SERVICE_ONE_ID) ) @pytest.mark.parametrize( ("template_data", "extra_args", "expected_count"), [ ( [], {}, 0, ), ( [], {"template_type": "email"}, 0, ), ( [ {"template_type": "email"}, {"template_type": "sms"}, ], {}, 2, ), ( [ {"template_type": "email"}, {"template_type": "sms"}, ], {"template_type": "email"}, 1, ), ], ) def test_client_returns_count_of_service_templates( notify_admin, mocker, template_data, extra_args, expected_count, ): mocker.patch( "app.service_api_client.get_service_templates", return_value={"data": template_data}, ) assert ( service_api_client.count_service_templates(SERVICE_ONE_ID, **extra_args) == expected_count ) @pytest.mark.parametrize( ( "client_method", "extra_args", "expected_cache_get_calls", "cache_value", "expected_api_calls", "expected_cache_set_calls", "expected_return_value", ), [ ( service_api_client.get_service, [SERVICE_ONE_ID], [call("service-{}".format(SERVICE_ONE_ID))], b'{"data_from": "cache"}', [], [], {"data_from": "cache"}, ), ( service_api_client.get_service, [SERVICE_ONE_ID], [call("service-{}".format(SERVICE_ONE_ID))], None, [call("/service/{}".format(SERVICE_ONE_ID))], [ call( "service-{}".format(SERVICE_ONE_ID), '{"data_from": "api"}', ex=604800, ) ], {"data_from": "api"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ call( "service-{}-template-{}-version-None".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ) ], b'{"data_from": "cache"}', [], [], {"data_from": "cache"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ call( "service-{}-template-{}-version-None".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ), ], None, [call("/service/{}/template/{}".format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID))], [ call( "service-{}-template-{}-version-None".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ), '{"data_from": "api"}', ex=604800, ), ], {"data_from": "api"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, 1], [ call( "service-{}-template-{}-version-1".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ) ], b'{"data_from": "cache"}', [], [], {"data_from": "cache"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, 1], [ call( "service-{}-template-{}-version-1".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ), ], None, [ call( "/service/{}/template/{}/version/1".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ) ], [ call( "service-{}-template-{}-version-1".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ), '{"data_from": "api"}', ex=604800, ), ], {"data_from": "api"}, ), ( service_api_client.get_service_templates, [SERVICE_ONE_ID], [call("service-{}-templates".format(SERVICE_ONE_ID))], b'{"data_from": "cache"}', [], [], {"data_from": "cache"}, ), ( service_api_client.get_service_templates, [SERVICE_ONE_ID], [call("service-{}-templates".format(SERVICE_ONE_ID))], None, [call("/service/{}/template?detailed=False".format(SERVICE_ONE_ID))], [ call( "service-{}-templates".format(SERVICE_ONE_ID), '{"data_from": "api"}', ex=604800, ) ], {"data_from": "api"}, ), ( service_api_client.get_service_template_versions, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ call( "service-{}-template-{}-versions".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ) ], b'{"data_from": "cache"}', [], [], {"data_from": "cache"}, ), ( service_api_client.get_service_template_versions, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ call( "service-{}-template-{}-versions".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ), ], None, [ call( "/service/{}/template/{}/versions".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ) ) ], [ call( "service-{}-template-{}-versions".format( SERVICE_ONE_ID, FAKE_TEMPLATE_ID ), '{"data_from": "api"}', ex=604800, ), ], {"data_from": "api"}, ), ], ) def test_returns_value_from_cache( mocker, client_method, extra_args, expected_cache_get_calls, cache_value, expected_return_value, expected_api_calls, expected_cache_set_calls, ): mock_redis_get = mocker.patch( "app.extensions.RedisClient.get", return_value=cache_value, ) mock_api_get = mocker.patch( "app.notify_client.NotifyAdminAPIClient.get", return_value={"data_from": "api"}, ) mock_redis_set = mocker.patch( "app.extensions.RedisClient.set", ) assert client_method(*extra_args) == expected_return_value assert mock_redis_get.call_args_list == expected_cache_get_calls assert mock_api_get.call_args_list == expected_api_calls assert mock_redis_set.call_args_list == expected_cache_set_calls @pytest.mark.parametrize( ("client", "method", "extra_args", "extra_kwargs"), [ (service_api_client, "update_service", [SERVICE_ONE_ID], {"name": "foo"}), ( service_api_client, "update_service_with_properties", [SERVICE_ONE_ID], {"properties": {}}, ), (service_api_client, "archive_service", [SERVICE_ONE_ID, []], {}), (service_api_client, "suspend_service", [SERVICE_ONE_ID], {}), (service_api_client, "resume_service", [SERVICE_ONE_ID], {}), (service_api_client, "remove_user_from_service", [SERVICE_ONE_ID, ""], {}), (service_api_client, "update_guest_list", [SERVICE_ONE_ID, {}], {}), ( service_api_client, "create_service_inbound_api", [SERVICE_ONE_ID] + [""] * 3, {}, ), ( service_api_client, "update_service_inbound_api", [SERVICE_ONE_ID] + [""] * 4, {}, ), (service_api_client, "add_reply_to_email_address", [SERVICE_ONE_ID, ""], {}), ( service_api_client, "update_reply_to_email_address", [SERVICE_ONE_ID] + [""] * 2, {}, ), (service_api_client, "delete_reply_to_email_address", [SERVICE_ONE_ID, ""], {}), (service_api_client, "add_sms_sender", [SERVICE_ONE_ID, ""], {}), (service_api_client, "update_sms_sender", [SERVICE_ONE_ID] + [""] * 2, {}), (service_api_client, "delete_sms_sender", [SERVICE_ONE_ID, ""], {}), ( service_api_client, "update_service_callback_api", [SERVICE_ONE_ID] + [""] * 4, {}, ), ( service_api_client, "create_service_callback_api", [SERVICE_ONE_ID] + [""] * 3, {}, ), (user_api_client, "add_user_to_service", [SERVICE_ONE_ID, uuid4(), [], []], {}), (invite_api_client, "accept_invite", [SERVICE_ONE_ID, uuid4()], {}), ], ) def test_deletes_service_cache( notify_admin, mock_get_user, mock_get_service_templates, mocker, client, method, extra_args, extra_kwargs, ): mocker.patch("app.notify_client.current_user", id="1") mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_request = mocker.patch( "notifications_python_client.base.BaseAPIClient.request" ) getattr(client, method)(*extra_args, **extra_kwargs) assert call("service-{}".format(SERVICE_ONE_ID)) in mock_redis_delete.call_args_list assert len(mock_request.call_args_list) == 1 @pytest.mark.parametrize( ("method", "extra_args", "expected_cache_deletes"), [ ( "create_service_template", ["name", "type_", "content", SERVICE_ONE_ID], [ "service-{}-templates".format(SERVICE_ONE_ID), ], ), ( "update_service_template", [FAKE_TEMPLATE_ID, "foo", "sms", "bar", SERVICE_ONE_ID], [ "service-{}-templates".format(SERVICE_ONE_ID), ], ), ( "redact_service_template", [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ "service-{}-templates".format(SERVICE_ONE_ID), ], ), ( "update_service_template_sender", [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, "foo"], [ "service-{}-templates".format(SERVICE_ONE_ID), ], ), ( "delete_service_template", [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ "service-{}-templates".format(SERVICE_ONE_ID), ], ), ( "archive_service", [SERVICE_ONE_ID, []], [ "service-{}-templates".format(SERVICE_ONE_ID), "service-{}".format(SERVICE_ONE_ID), ], ), ], ) def test_deletes_caches_when_modifying_templates( notify_admin, mock_get_user, mocker, method, extra_args, expected_cache_deletes, ): mocker.patch("app.notify_client.current_user", id="1") mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" ) mock_request = mocker.patch( "notifications_python_client.base.BaseAPIClient.request" ) getattr(service_api_client, method)(*extra_args) assert mock_redis_delete.call_args_list == [call(x) for x in expected_cache_deletes] assert len(mock_request.call_args_list) == 1 if method != "create_service_template": # no deletes for template cach on create_service_template assert len(mock_redis_delete_by_pattern.call_args_list) == 1 assert mock_redis_delete_by_pattern.call_args_list[0] == call( f"service-{SERVICE_ONE_ID}-template-*" ) def test_deletes_cached_users_when_archiving_service( mocker, mock_get_service_templates ): mocker.patch( "app.notify_client.service_api_client.ServiceAPIClient.check_inactive_user" ) mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" ) mocker.patch( "notifications_python_client.base.BaseAPIClient.request", return_value={"data": ""}, ) service_api_client.archive_service(SERVICE_ONE_ID, ["my-user-id1", "my-user-id2"]) assert ( call("user-my-user-id1", "user-my-user-id2") in mock_redis_delete.call_args_list ) assert ( call(f"service-{SERVICE_ONE_ID}-template-*") in mock_redis_delete_by_pattern.call_args_list ) def test_client_gets_guest_list(mocker): client = ServiceAPIClient() mock_get = mocker.patch.object(client, "get", return_value=["a", "b", "c"]) response = client.get_guest_list("foo") assert response == ["a", "b", "c"] mock_get.assert_called_once_with( url="/service/foo/guest-list", ) def test_client_updates_guest_list(mocker): client = ServiceAPIClient() mock_put = mocker.patch.object(client, "put") client.update_guest_list("foo", data=["a", "b", "c"]) mock_put.assert_called_once_with( url="/service/foo/guest-list", data=["a", "b", "c"], ) def test_client_doesnt_delete_service_template_cache_when_none_exist( notify_admin, mock_get_user, mock_get_service_templates_when_no_templates_exist, mocker, ): mocker.patch("app.notify_client.current_user", id="1") mocker.patch("notifications_python_client.base.BaseAPIClient.request") mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" ) service_api_client.update_reply_to_email_address( SERVICE_ONE_ID, uuid4(), "foo@bar.com" ) assert len(mock_redis_delete.call_args_list) == 1 assert mock_redis_delete.call_args_list[0] == call( "service-{}".format(SERVICE_ONE_ID) ) assert len(mock_redis_delete_by_pattern.call_args_list) == 1 def test_client_deletes_service_template_cache_when_service_is_updated( notify_admin, mock_get_user, mocker ): mocker.patch("app.notify_client.current_user", id="1") mocker.patch("notifications_python_client.base.BaseAPIClient.request") mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") mock_redis_delete_by_pattern = mocker.patch( "app.extensions.RedisClient.delete_by_pattern" ) service_api_client.update_reply_to_email_address( SERVICE_ONE_ID, uuid4(), "foo@bar.com" ) assert len(mock_redis_delete.call_args_list) == 1 assert mock_redis_delete.call_args_list[0] == call(f"service-{SERVICE_ONE_ID}") assert mock_redis_delete_by_pattern.call_args_list[0] == call( f"service-{SERVICE_ONE_ID}-template-*" ) def test_client_updates_service_with_allowed_attributes( mocker, ): client = ServiceAPIClient() mock_post = mocker.patch.object(client, "post", return_value={"data": {"id": None}}) mocker.patch("app.notify_client.current_user", id="123") allowed_attributes = [ "active", "consent_to_research", "contact_link", "count_as_live", "email_from", "free_sms_fragment_limit", "go_live_at", "go_live_user", "message_limit", "name", "notes", "organization_type", "permissions", "prefix_sms", "rate_limit", "reply_to_email_address", "research_mode", "restricted", "sms_sender", "volume_email", "volume_sms", ] attrs_dict = {} for attr in allowed_attributes: attrs_dict[attr] = "value" client.update_service(SERVICE_ONE_ID, **attrs_dict) mock_post.assert_called_once_with( f"/service/{SERVICE_ONE_ID}", {**{"created_by": "123"}, **attrs_dict} )