diff --git a/docker/integtest/5-assert-on-results.sh b/docker/integtest/5-assert-on-results.sh index bb705c68..aeb3e271 100755 --- a/docker/integtest/5-assert-on-results.sh +++ b/docker/integtest/5-assert-on-results.sh @@ -14,3 +14,11 @@ if [[ "${num_exceptions}" -ne "${num_exceptions_expected}" ]]; then echo "Got ${num_exceptions} exceptions but expected ${num_exceptions_expected}" >&2 exit 2 fi + +num_users="$(curl -fs 'http://nginx:8888/api/email/metrics/users/developer1.lokole.ca' -u "${REGISTRATION_CREDENTIALS}" | jq -r '.users')" +num_users_expected=1 + +if [[ "${num_users}" -ne "${num_users_expected}" ]]; then + echo "Got ${num_users} users but expected ${num_users_expected}" >&2 + exit 3 +fi diff --git a/opwen_email_server/actions.py b/opwen_email_server/actions.py index 236a125c..6afb8731 100644 --- a/opwen_email_server/actions.py +++ b/opwen_email_server/actions.py @@ -192,10 +192,10 @@ def _store_users(self, resource_id): num_stored = 0 for user in users: email = user['email'] - self._user_storage.store_object(email, user) + domain = get_domain(email) + self._user_storage.store_object(f'{domain}/{email}', user) num_stored += 1 - domain = email.split('@')[1] self.log_event(events.USER_STORED_FROM_CLIENT, {'domain': domain, 'num_users': num_stored}) # noqa: E501 # yapf: disable @@ -429,19 +429,35 @@ def _action(self, domain, **auth_args): # type: ignore return 'OK', 200 +class CalculateNumberOfUsersMetric(_Action): + def __init__(self, auth: AzureAuth, user_storage: AzureObjectStorage): + self._auth = auth + self._user_storage = user_storage + + def _action(self, domain, **auth_args): # type: ignore + if not self._auth.is_owner(domain, auth_args.get('user')): + return 'client does not belong to the user', 403 + + users = sum(1 for _ in self._user_storage.iter(f'{domain}/')) + + return { + 'users': users, + } + + class CalculatePendingEmailsMetric(_Action): def __init__(self, auth: AzureAuth, pending_factory: Callable[[str], AzureTextStorage]): self._auth = auth self._pending_factory = pending_factory - def _action(self, client_domain, **auth_args): # type: ignore - client_id = self._auth.client_id_for(client_domain) + def _action(self, domain, **auth_args): # type: ignore + client_id = self._auth.client_id_for(domain) if not client_id: - self.log_event(events.UNKNOWN_CLIENT_DOMAIN, {'client_domain': client_domain}) # noqa: E501 # yapf: disable + self.log_event(events.UNKNOWN_CLIENT_DOMAIN, {'domain': domain}) # noqa: E501 # yapf: disable return 'unknown client domain', 404 - pending_storage = self._pending_factory(client_domain) + pending_storage = self._pending_factory(domain) pending_emails = sum(1 for _ in pending_storage.iter()) return { diff --git a/opwen_email_server/integration/connexion.py b/opwen_email_server/integration/connexion.py index be4a43d3..dc525643 100644 --- a/opwen_email_server/integration/connexion.py +++ b/opwen_email_server/integration/connexion.py @@ -1,4 +1,5 @@ from opwen_email_server import config +from opwen_email_server.actions import CalculateNumberOfUsersMetric from opwen_email_server.actions import CalculatePendingEmailsMetric from opwen_email_server.actions import CreateClient from opwen_email_server.actions import DeleteClient @@ -13,6 +14,7 @@ from opwen_email_server.integration.azure import get_email_storage from opwen_email_server.integration.azure import get_pending_storage from opwen_email_server.integration.azure import get_raw_email_storage +from opwen_email_server.integration.azure import get_user_storage from opwen_email_server.integration.celery import inbound_store from opwen_email_server.integration.celery import register_client from opwen_email_server.integration.celery import written_store @@ -62,6 +64,11 @@ ), ) +metrics_users = CalculateNumberOfUsersMetric( + auth=get_auth(), + user_storage=get_user_storage(), +) + metrics_pending = CalculatePendingEmailsMetric( auth=get_auth(), pending_factory=get_pending_storage, diff --git a/opwen_email_server/swagger/client-metrics.yaml b/opwen_email_server/swagger/client-metrics.yaml index 0a61c839..497d1c6d 100644 --- a/opwen_email_server/swagger/client-metrics.yaml +++ b/opwen_email_server/swagger/client-metrics.yaml @@ -8,7 +8,7 @@ basePath: '/api/email/metrics' paths: - '/pending/{client_domain}': + '/pending/{domain}': get: operationId: opwen_email_server.integration.connexion.metrics_pending @@ -16,7 +16,7 @@ paths: produces: - application/json parameters: - - $ref: '#/parameters/ClientDomain' + - $ref: '#/parameters/Domain' responses: 200: description: The number of pending emails for the client. @@ -27,6 +27,25 @@ paths: security: - basic: [] + '/users/{domain}': + + get: + operationId: opwen_email_server.integration.connexion.metrics_users + summary: Check how many users are registered for the client. + produces: + - application/json + parameters: + - $ref: '#/parameters/Domain' + responses: + 200: + description: The number of users registered for the client. + schema: + $ref: '#/definitions/UsersMetric' + 403: + description: The client does not belong to the user. + security: + - basic: [] + securityDefinitions: basic: type: basic @@ -34,8 +53,8 @@ securityDefinitions: parameters: - ClientDomain: - name: client_domain + Domain: + name: domain description: Domain of the Lokole client. in: path type: string @@ -51,3 +70,12 @@ definitions: type: integer required: - pending_emails + + UsersMetric: + type: object + properties: + users: + description: The number of users. + type: integer + required: + - users diff --git a/tests/opwen_email_server/test_actions.py b/tests/opwen_email_server/test_actions.py index 79a8ca94..8a980a3c 100644 --- a/tests/opwen_email_server/test_actions.py +++ b/tests/opwen_email_server/test_actions.py @@ -241,7 +241,7 @@ def _test_200(self, attachment_content_bytes, attachment_content_base64): self.email_storage.store_object.assert_called_once_with(email_id, server_email) self.next_task.assert_called_once_with(email_id) self.client_storage.fetch_objects.assert_any_call(resource_id, (sync.USERS_FILE, from_jsonl_bytes)) - self.user_storage.store_object.assert_called_once_with(user_email, user) + self.user_storage.store_object.assert_called_once_with(f'developer1.lokole.ca/{user_email}', user) self.client_storage.delete.assert_called_once_with(resource_id) def _execute_action(self, *args, **kwargs): @@ -701,6 +701,48 @@ def _execute_action(self, *args, **kwargs): return action(*args, **kwargs) +class CalculateNumberOfUsersMetricTests(TestCase): + def setUp(self): + self.auth = Mock() + self.user_storage = Mock() + + def test_403(self): + domain = 'test.com' + user = 'user' + + self.auth.is_owner.return_value = False + + _, status = self._execute_action(domain, user=user) + + self.assertEqual(status, 403) + + def test_200(self): + domain = 'test.com' + user = 'user' + users = [ + 'test.com/user1', + 'test.com/user2', + 'test.com/user3', + ] + + self.auth.is_owner.return_value = True + self.user_storage.iter.return_value = users + + response = self._execute_action(domain, user=user) + + self.assertEqual(response['users'], len(users)) + self.auth.is_owner.assert_called_once_with(domain, user) + self.user_storage.iter.assert_called_once_with(f'{domain}/') + + def _execute_action(self, *args, **kwargs): + action = actions.CalculateNumberOfUsersMetric( + auth=self.auth, + user_storage=self.user_storage, + ) + + return action(*args, **kwargs) + + class CalculatePendingEmailsMetricTests(TestCase): def setUp(self): self.auth = Mock() @@ -708,18 +750,18 @@ def setUp(self): self.pending_factory = MagicMock() def test_404(self): - client_domain = 'does.not.exist' + domain = 'does.not.exist' client_id = None self.auth.client_id_for.return_value = client_id - _, status = self._execute_action(client_domain) + _, status = self._execute_action(domain) self.assertEqual(status, 404) - self.auth.client_id_for.assert_called_once_with(client_domain) + self.auth.client_id_for.assert_called_once_with(domain) def test_200(self): - client_domain = 'test.com' + domain = 'test.com' client_id = 'e8e5caa4-4ee6-4e7f-99c9-e231b6a27a9f' pending_email_ids = [ '1de2ceb6-4f82-4cad-86ac-815bcbcb801c', @@ -731,11 +773,11 @@ def test_200(self): self.pending_factory.return_value = self.pending_storage self.pending_storage.iter.return_value = pending_email_ids - response = self._execute_action(client_domain) + response = self._execute_action(domain) self.assertEqual(response['pending_emails'], len(pending_email_ids)) - self.auth.client_id_for.assert_called_once_with(client_domain) - self.pending_factory.assert_called_once_with(client_domain) + self.auth.client_id_for.assert_called_once_with(domain) + self.pending_factory.assert_called_once_with(domain) self.pending_storage.iter.assert_called_once_with() def _execute_action(self, *args, **kwargs):