Details
| ID | Zoom Mail |
|---|---|
| Provider | Zoom |
| Category | |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Enables interaction with the Zoom Mail API.
Configure Zoom Mail in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://api.zoom.us/v2) | True | |
| Fetch incidents | False | |
| Incident type | False | |
| Maximum number of alerts per fetch | False | |
| Client ID | True | |
| Client Secret | True | |
| Account ID | True | |
| First fetch time | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Incidents Fetch Interval | False | |
| Fetch Mailbox | Mailbox to fetch incidents from | False |
| Fetch Query | Elastic query to filter messages in the specified inbox. | False |
| Fetch Labels | Specify the folder that the messages will be fetched from. | False |
| Include Threads when Fetching | False |
Scopes Required
Classic Scopes
- mail:read
- mail:write
- user:read
Granular Scopes
- email:read:list_msgs
- email:write:trash_msg
- email:read:list_threads
- email:read:thread
- email:read:attachment
- email:write:send_msg
- email:read:profile
- user:read:list_users:admin
Additional Resources
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
zoom-mail-email-move-trash
Move a message to the trash.
Base Command
zoom-mail-email-move-trash
Input
| Argument Name | Description | Required |
|---|---|---|
| The target mailbox to delete the email from. | Optional | |
| message_id | The message_id of the message to delete. | Required |
Context Output
There is no context output for this command.
zoom-mail-email-list
Lists the messages in the user’s mailbox.
Base Command
zoom-mail-email-list
Input
| Argument Name | Description | Required |
|---|---|---|
| The target mailbox to list emails. | Optional | |
| query | Query to filter emails within the given mailbox. | Optional |
| max_results | Maximum number of emails to list. | Optional |
| page_token | The token for the next page of results. | Optional |
| include_spam_trash | Whether or not to include spam or trash messages in the results. | Optional |
| message_id | The immutable message ID. | Optional |
| format | The format to return the message with. Possible values are: full, metadata, minimal. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ZoomMail.Email.messages.id | string | The ID of the email message. |
| ZoomMail.Email.messages.threadId | string | The ID of the email thread. |
| ZoomMail.Email.resultSizeEstimate | number | The estimated amount of messages found. |
| ZoomMail.Email.messages.labelIds | string | The labels assigned to the email. |
| ZoomMail.Email.messages.snippet | string | A snippet of the email content. |
| ZoomMail.Email.messages.historyId | string | The history ID of the email. |
| ZoomMail.Email.messages.internalDate | date | The internal date of the email. |
| ZoomMail.Email.messages.expiration | number | The expiration time of the email. |
| ZoomMail.Email.messages.lastMoved | date | The last moved timestamp of the email. |
| ZoomMail.Email.messages.sendTime | date | The send time of the email. |
| ZoomMail.Email.messages.userScheduled | boolean | Indicates if the email was user scheduled. |
| ZoomMail.Email.messages.manifest | string | The manifest of the email. |
| ZoomMail.Email.messages.originalMime | string | The original MIME of the email. |
| ZoomMail.Email.messages.payload.partId | string | The part ID of the email payload. |
| ZoomMail.Email.messages.payload.mimeType | string | The MIME type of the email payload. |
| ZoomMail.Email.messages.payload.filename | string | The filename of the email payload. |
| ZoomMail.Email.messages.payload.headers | unknown | The headers of the email payload. |
| ZoomMail.Email.messages.payload.body.attachmentId | string | The attachment ID of the email body. |
| ZoomMail.Email.messages.payload.body.size | number | The size of the email body. |
| ZoomMail.Email.messages.payload.body.data | string | The data of the email body. |
| ZoomMail.Email.messages.payload.parts | unknown | The parts of the email payload. |
| ZoomMail.Email.sizeEstimate | number | The size estimate of the email. |
| ZoomMail.Email.raw | string | The raw content of the email. |
zoom-mail-thread-list
Get an email thread.
Base Command
zoom-mail-thread-list
Input
| Argument Name | Description | Required |
|---|---|---|
| The target mailbox to list emails. | Optional | |
| thread_id | Unique identifier for the email thread you want to retrieve. | Optional |
| format | Specifies the format in which the email messages in the thread should be returned. Possible values are: full, metadata, minimal. | Optional |
| metadata_headers | When the format is set to metadata, this argument allows you to specify which email headers should be included in the response. | Optional |
| max_results | Default is 50. | Optional |
| page_token | The token for the next page of results. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ZoomMail.Thread.id | string | The ID of the email thread. |
| ZoomMail.Thread.status | string | The status of the email thread. |
| ZoomMail.Thread.threadName | string | The name of the email thread. |
| ZoomMail.Thread.messages | unknown | The messages found in the email thread. |
zoom-mail-email-attachment-get
Get an email attachment.
Base Command
zoom-mail-email-attachment-get
Input
| Argument Name | Description | Required |
|---|---|---|
| The email address of the inbox. | Optional | |
| message_id | The immutable message ID. | Required |
| attachment_id | The immutable attachment ID. | Required |
Context Output
There is no context output for this command.
zoom-mail-send-email
Sends an email message with support for plain text, HTML, and attachments.
Base Command
zoom-mail-send-email
Input
| Argument Name | Description | Required |
|---|---|---|
| from | The sender address. | Optional |
| to | The recipient address. | Required |
| subject | The subject of the email. | Required |
| body | The plain text body of the email. | Optional |
| html_body | The HTML body of the email. | Optional |
| attachments | Comma-separated list of War Room entry IDs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ZoomMail.Email.id | string | The id of the sent email. |
| ZoomMail.Email.threadId | string | The id for the thread of the sent email. |
zoom-mail-mailbox-profile-get
Retrieves the mailbox profile.
Base Command
zoom-mail-mailbox-profile-get
Input
| Argument Name | Description | Required |
|---|---|---|
| The target mailbox. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ZoomMail.Profile.status | string | The status of the mailbox profile. |
| ZoomMail.Profile.emailAddress | string | The email address assigned to the mailbox profile. |
| ZoomMail.Profile.messagesTotal | number | The total number of messages. |
| ZoomMail.Profile.threadsTotal | number | The total number of threads. |
zoom-mail-user-list
Lists the available users.
Base Command
zoom-mail-user-list
Input
| Argument Name | Description | Required |
|---|---|---|
| status | The status of the User. | Optional |
| limit | The max amount of users to return per page. | Optional |
| role_id | The ID for the role of the User. | Optional |
| page_number | The page number of results. | Optional |
| include_fields | Indicates whether or not to include fields. | Optional |
| next_token | The token used to fetch the next page. | Optional |
| license | Indicates if the user is licensed or not. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ZoomMail.User.role_id | string | The ID of the users role. |
| ZoomMail.User.display_name | string | The display name of the user. |
| ZoomMail.User.id | string | The ID of the user. |
| ZoomMail.User.email | unknown | The email for the user. |
Configuration parameters
url— Server URL (e.g., https://api.zoom.us/v2) (required)credentials— Client ID (required)account_id— Account ID (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typemax_fetch— Maximum number of alerts per fetchfirst_fetch— First fetch timeincidentFetchInterval— Incidents Fetch Intervalfetch_from— Default Mailboxfetch_query— Fetch Queryfetch_labels— Fetch Labelsfetch_threads— Include Threads when Fetching
Commands (7)
-
zoom-mail-email-attachment-getGet an email attachment.
-
zoom-mail-email-listLists the messages in the user's mailbox.
-
zoom-mail-email-move-trashMove a message to the trash.
-
zoom-mail-mailbox-profile-getRetrieves the mailbox profile.
-
zoom-mail-send-emailSends an email message with support for plain text, HTML, and attachments.
-
zoom-mail-thread-listGet an email thread.
-
zoom-mail-user-listLists the available users.
import base64 import unittest from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from typing import Any from unittest.mock import patch, MagicMock import json from CommonServerPython import CommandResults, DemistoException from ZoomMail import ( ZoomMailClient, the_testing_module, fetch_incidents, get_email_thread_command, trash_email_command, list_emails_command, get_email_attachment_command, get_mailbox_profile_command, list_users_command, process_attachments, create_email_message, attach_files_to_email, attach_file, main, safe_bytes_to_string, decode_base64, correct_base64_errors, ) def load_test_data(json_path): with open(json_path) as f: return json.load(f) class TestZoomMailClient(unittest.TestCase): def setUp(self): """Prepare environment for testing.""" self.base_url = "https://api.zoom.us/v2" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "test@testingzmail.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.BaseClient._http_request") def test_obtain_access_token_success(self, mock_http_request): """ Tests the successful retrieval of an access token from the API. Given: - Mock response simulates a successful API call returning an access token. When: - Client requests an access token. Then: - Client should receive a valid token and update internal states appropriately. """ mock_http_request.return_value = {"access_token": "test_token"} result = self.client.obtain_access_token() assert result["success"] assert result["token"] == "test_token" assert self.client.access_token == "test_token" assert self.client.token_time is not None @patch("ZoomMail.BaseClient._http_request") def test_obtain_access_token_failure(self, mock_http_request): """ Tests the failure to retrieve an access token from the API. Given: - Mock response simulates an API failure with empty response. When: - Client attempts to obtain an access token. Then: - The response should indicate failure and contain an appropriate error message. """ mock_http_request.return_value = {} result = self.client.obtain_access_token() assert not result["success"] assert "error" in result class TestZoomMailClientGetEmailThread(unittest.TestCase): """ Unit tests for the ZoomMailClient's method get_email_thread to ensure it handles different scenarios correctly. """ def setUp(self): """ Prepare environment for each test. """ self.base_url = "https://api.zoom.us/v2" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "testing@zmail.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) self.email = "user@example.com" self.thread_id = "12345" self.format = "full" self.metadata_headers = "From,To" self.maxResults = "50" self.pageToken = "" @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_thread_success(self, mock_http_request): """ Tests successful retrieval of an email thread. Given: - A valid response from the API for an email thread retrieval. When: - Requesting the email thread using valid parameters. Then: - Should return the correct thread details. """ mock_thread_response = load_test_data("test_data/thread/thread_list_response.json") mock_http_request.return_value = mock_thread_response response = self.client.get_email_thread( self.email, self.thread_id, self.format, self.metadata_headers, self.maxResults, self.pageToken ) assert response["id"] == self.thread_id assert isinstance(response["messages"], list) assert len(response["messages"]) == 4 @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_thread_failure(self, mock_http_request): """ Tests the client's response to a failed API request. Given: - An API error occurs during the request. When: - Retrieving an email thread. Then: - Should raise an Exception and provide a relevant error message. """ mock_http_request.side_effect = Exception("API Request Failed") with self.assertRaises(Exception) as context: self.client.get_email_thread( self.email, self.thread_id, self.format, self.metadata_headers, self.maxResults, self.pageToken ) assert "API Request Failed" in str(context.exception) @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_thread_invalid_input(self, mock_http_request): """ Tests handling of invalid thread IDs. Given: - The API returns an error response for invalid input. When: - An invalid thread ID is used to retrieve an email thread. Then: - Should receive an error response indicating the invalid input. """ mock_http_request.return_value = {"error": "Invalid thread ID"} response = self.client.get_email_thread( self.email, "invalid_thread_id", self.format, self.metadata_headers, self.maxResults, self.pageToken ) assert "error" in response assert response["error"] == "Invalid thread ID" @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_thread_with_no_email_provided_uses_default(self, mock_http_request): """ Tests the use of default email when none is provided. Given: - An API call is made without specifying the email address. When: - The email parameter is None, expecting the system to use the default email address. Then: - Should use the default email address and execute the API call successfully. """ mock_response = {"id": self.thread_id, "messages": []} mock_http_request.return_value = mock_response response = self.client.get_email_thread( None, self.thread_id, self.format, self.metadata_headers, self.maxResults, self.pageToken ) mock_http_request.assert_called_with( method="GET", url_suffix=f"/emails/mailboxes/{self.default_email}/threads/{self.thread_id}", params={ "format": self.format, "metadata_headers": self.metadata_headers, "maxResults": self.maxResults, "pageToken": self.pageToken, }, ) assert response == mock_response class TestZoomMailClientTrashEmail(unittest.TestCase): """ Tests for the ZoomMailClient's trash_email method to ensure it correctly handles email trashing under various scenarios. """ def setUp(self): """ Set up common variables for tests. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) self.message_id = "123456789" @patch("ZoomMail.ZoomMailClient._http_request") def test_trash_email_with_email_provided(self, mock_http_request): """ Test that trashing an email successfully sends a request to the server when an email address is provided. Given: - An email address and message ID to trash. When: - The trash_email method is called with the email address. Then: - A POST request should be made to the correct API endpoint. - The server should return a success response. """ email = "user@example.com" expected_url_suffix = f"/emails/mailboxes/{email}/messages/{self.message_id}/trash" mock_http_request.return_value = {"success": True} response = self.client.trash_email(email, self.message_id) mock_http_request.assert_called_once_with(method="POST", url_suffix=expected_url_suffix) assert response == {"success": True} @patch("ZoomMail.ZoomMailClient._http_request") def test_trash_email_with_no_email_provided_uses_default(self, mock_http_request): """ Test that trashing an email uses the default email when none is provided. Given: - A message ID to trash and no email address provided. When: - The trash_email method is called without an email address. Then: - A POST request should be made using the default email to the correct API endpoint. - The server should return a success response. """ expected_url_suffix = f"/emails/mailboxes/{self.default_email}/messages/{self.message_id}/trash" mock_http_request.return_value = {"success": True} response = self.client.trash_email(None, self.message_id) mock_http_request.assert_called_once_with(method="POST", url_suffix=expected_url_suffix) assert response == {"success": True} @patch("ZoomMail.ZoomMailClient._http_request") def test_trash_email_raises_error_if_no_default_email_set(self, mock_http_request): """ Test that trashing an email raises an error if no email is provided and no default email is set. Given: - A message ID to trash and no email address or default email set. When: - The trash_email method is called without an email and without a default email. Then: - The method should raise a ValueError due to missing email information. """ client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) with self.assertRaises(ValueError) as context: client_without_default.trash_email(None, self.message_id) assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientListEmails(unittest.TestCase): """ Tests for the ZoomMailClient's list_emails method to ensure it handles email listing under various scenarios correctly. """ def setUp(self): """ Set up common test components and dependencies. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_list_emails_with_all_parameters(self, mock_http_request): """ Test listing emails successfully with all filtering parameters provided. Given: - A complete set of filtering parameters for listing emails. When: - The list_emails method is invoked with these parameters. Then: - A GET request should be made to the API with the correct parameters. - The API should return a list of emails. """ email = "user@example.com" expected_params = { "maxResults": "100", "pageToken": "token123", "q": "subject:hello", "includeSpamTrash": "true", } mock_http_request.return_value = {"messages": []} response = self.client.list_emails( email=email, max_results="100", page_token="token123", label_ids="INBOX,SENT", query="subject:hello", include_spam_trash=True, ) mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{email}/messages", params=expected_params, ) assert response == {"messages": []} @patch("ZoomMail.ZoomMailClient._http_request") def test_list_emails_uses_default_email_when_none_provided(self, mock_http_request): """ Test that the default email address is used for listing emails when no specific email is provided. Given: - No specific email address is provided for listing emails. When: - The list_emails method is called without an email address. Then: - The API should use the default email address for the request. - A GET request should retrieve the emails from the mailbox associated with the default email. """ expected_params = { "maxResults": "50", "pageToken": "", "q": "", "includeSpamTrash": "false", } mock_http_request.return_value = {"messages": []} response = self.client.list_emails(email=None) mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{self.default_email}/messages", params=expected_params, ) assert response == {"messages": []} @patch("ZoomMail.ZoomMailClient._http_request") def test_list_emails_raises_error_if_no_default_email_set(self, mock_http_request): """ Test that an error is raised when attempting to list emails without providing an email and no default is set. Given: - No default email is set and no email address is provided. When: - The list_emails method is called without an email address. Then: - The method should raise a ValueError indicating that no email address was provided and no default is set. """ client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) with self.assertRaises(ValueError) as context: client_without_default.list_emails(email=None) assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientGetEmailAttachment(unittest.TestCase): """ Tests for the ZoomMailClient's get_email_attachment method to ensure it handles attachment retrieval correctly. """ def setUp(self): """ Set up common test components and dependencies. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_attachment_success(self, mock_http_request): """ Test successful retrieval of an email attachment. Given: - All parameters including email, message ID, and attachment ID are provided. When: - The get_email_attachment method is called with these parameters. Then: - A GET request should be made correctly and the expected attachment data should be returned. """ email = "user@example.com" message_id = "12345" attachment_id = "67890" mock_http_request.return_value = {"data": "base64data", "more_info": "details"} response = self.client.get_email_attachment(email, message_id, attachment_id) mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{email}/messages/{message_id}/attachments/{attachment_id}", ) assert response == {"data": "base64data", "more_info": "details"} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_attachment_uses_default_email_when_none_provided(self, mock_http_request): """ Test the default email address is used for retrieving an email attachment when no specific email is provided. Given: - Message ID and attachment ID are provided, but no email address is provided. When: - The get_email_attachment method is called without an email address. Then: - The API should use the default email address for the request and successfully return the attachment data. """ message_id = "12345" attachment_id = "67890" mock_http_request.return_value = {"data": "base64data"} response = self.client.get_email_attachment(None, message_id, attachment_id) mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{self.default_email}/messages/{message_id}/attachments/{attachment_id}", ) assert response == {"data": "base64data"} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_attachment_raises_error_if_no_default_email_set(self, mock_http_request): """ Test that an error is raised when attempting to retrieve an email attachment without providing an email and no default is set. Given: - No default email is set and no email address is provided. When: - The get_email_attachment method is called without an email address. Then: - The method should raise a ValueError indicating that no email address was provided and no default is set. """ client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) message_id = "12345" attachment_id = "67890" with self.assertRaises(ValueError) as context: client_without_default.get_email_attachment(None, message_id, attachment_id) assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientGetEmailMessage(unittest.TestCase): """ Tests for the ZoomMailClient's get_email_message method to ensure it properly retrieves email messages. """ def setUp(self): """ Set up common test components and dependencies. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_message_success(self, mock_http_request): """ Test successful retrieval of an email message. Given: - An email address, message ID, message format, and metadata headers are provided. When: - The get_email_message method is called with these parameters. Then: - A GET request should be made correctly, and the expected email details should be returned. """ email = "user@example.com" message_id = "12345" msg_format = "full" metadata_headers = "From,To" mock_http_request.return_value = {"id": message_id, "subject": "Test Email"} response = self.client.get_email_message(email, message_id, msg_format, metadata_headers) mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{email}/messages/{message_id}", params={"format": msg_format, "metadata_headers": metadata_headers}, ) assert response == {"id": message_id, "subject": "Test Email"} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_message_uses_default_email_when_none_provided(self, mock_http_request): """ Test the default email address is used for retrieving an email message when no specific email is provided. Given: - Only a message ID is provided, without an email address. When: - The get_email_message method is called without an email address. Then: - The API should use the default email address for the request and successfully return the email message. """ message_id = "12345" mock_http_request.return_value = {"id": message_id} response = self.client.get_email_message(None, message_id, "full", "") mock_http_request.assert_called_once_with( method="GET", url_suffix=f"/emails/mailboxes/{self.default_email}/messages/{message_id}", params={"format": "full", "metadata_headers": ""}, ) assert response == {"id": message_id} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_email_message_raises_error_if_no_default_email_set(self, mock_http_request): """ Test that an error is raised when attempting to retrieve an email message without providing an email and no default is set. Given: - No default email is set and no email address is provided. When: - The get_email_message method is called without an email address. Then: - The method should raise a ValueError indicating that no email address was provided and no default is set. """ client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) message_id = "12345" with self.assertRaises(ValueError) as context: client_without_default.get_email_message(None, message_id, "full", "") assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientSendEmail(unittest.TestCase): """ Tests for the ZoomMailClient's send_email method to ensure it properly handles sending emails. """ def setUp(self): """ Set up common test components and dependencies for each test case. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_send_email_success(self, mock_http_request): """ Test successful sending of an email. Given: - An email address and raw_message are provided. When: - The send_email method is called with these parameters. Then: - A POST request should be made correctly, and the expected email send confirmation should be returned. """ email = "user@example.com" raw_message = "encoded_message" mock_http_request.return_value = {"status": "sent", "id": "123"} response = self.client.send_email(email, raw_message) mock_http_request.assert_called_once_with( method="POST", url_suffix=f"/emails/mailboxes/{email}/messages/send", json_data={"raw": raw_message}, ) assert response == {"status": "sent", "id": "123"} @patch("ZoomMail.ZoomMailClient._http_request") def test_send_email_uses_default_email_when_none_provided(self, mock_http_request): """ Test using default email when none is provided during the email sending operation. Given: - No email is provided, but a raw_message is specified. When: - The send_email method is called without an email address. Then: - The API should use the default email address for the send operation, confirming successful sending. """ raw_message = "encoded_message" mock_http_request.return_value = {"status": "sent"} response = self.client.send_email(None, raw_message) mock_http_request.assert_called_once_with( method="POST", url_suffix=f"/emails/mailboxes/{self.default_email}/messages/send", json_data={"raw": raw_message}, ) assert response == {"status": "sent"} @patch("ZoomMail.ZoomMailClient._http_request") def test_send_email_raises_error_if_no_default_email_set(self, mock_http_request): """ Test that an error is raised if no email is provided and no default is set during the email sending operation. Given: - No default email is set and no email address is provided. When: - The send_email method is called without an email address. Then: - The method should raise a ValueError indicating that no email address was provided and no default is set. """ client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) raw_message = "encoded_message" with self.assertRaises(ValueError) as context: client_without_default.send_email(None, raw_message) assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientGetMailboxProfile(unittest.TestCase): def setUp(self): self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_get_mailbox_profile_success(self, mock_http_request): """Test successful retrieval of mailbox profile.""" email = "user@example.com" mock_http_request.return_value = {"email": email, "status": "active"} # Call the function response = self.client.get_mailbox_profile(email) # Assert correct HTTP request call mock_http_request.assert_called_once_with(method="GET", url_suffix=f"/emails/mailboxes/{email}/profile") # Assert response contains the expected keys assert response == {"email": email, "status": "active"} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_mailbox_profile_uses_default_email_when_none_provided(self, mock_http_request): """Test using default email when none is provided.""" mock_http_request.return_value = { "email": self.default_email, "status": "active", } # Call the function without providing an email response = self.client.get_mailbox_profile(None) # Assert correct HTTP request call mock_http_request.assert_called_once_with(method="GET", url_suffix=f"/emails/mailboxes/{self.default_email}/profile") # Assert response matches the mock assert response == {"email": self.default_email, "status": "active"} @patch("ZoomMail.ZoomMailClient._http_request") def test_get_mailbox_profile_raises_error_if_no_default_email_set(self, mock_http_request): """Test that method raises an error if no email is provided and no default is set.""" client_without_default = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, None, # No default email set ) # Expecting ValueError when no default email is set and none is provided with self.assertRaises(ValueError) as context: client_without_default.get_mailbox_profile(None) assert str(context.exception) == "No email address provided and no default set." class TestZoomMailClientListUsers(unittest.TestCase): def setUp(self): """Set up the environment for each test. Given: - Base URL, client ID, client secret, account ID, and a default email are provided to initialize the ZoomMailClient. """ self.base_url = "https://api.example.com" self.client_id = "test_client_id" self.client_secret = "test_client_secret" self.account_id = "test_account_id" self.default_email = "default@example.com" self.client = ZoomMailClient( self.base_url, self.client_id, self.client_secret, self.account_id, self.default_email, ) @patch("ZoomMail.ZoomMailClient._http_request") def test_list_users_basic(self, mock_http_request): """Test listing users without pagination. When: - No pagination or other additional parameters are provided. Then: - The function should use default parameters to list users. - Assert that the HTTP request was made correctly. - Verify the response matches the expected empty list of users. """ mock_http_request.return_value = {"users": [], "total": 0} response = self.client.list_users() expected_params = { "status": "active", "page_size": 30, "role_id": "", "page_number": "1", "include_fields": "", "next_page_token": "", "license": "", } mock_http_request.assert_called_once_with(method="GET", url_suffix="/users", params=expected_params) assert response == {"users": [], "total": 0} @patch("ZoomMail.ZoomMailClient._http_request") def test_list_users_with_pagination(self, mock_http_request): """Test listing users with a pagination token. When: - A next_page_token is provided to fetch a specific page of users. Then: - The function should make an HTTP request including the pagination token. - Verify the response matches the expected empty list of users. """ mock_http_request.return_value = {"users": [], "total": 0} response = self.client.list_users(next_page_token="abc123") mock_http_request.assert_called_once_with(method="GET", url_suffix="/users", params={"next_page_token": "abc123"}) assert response == {"users": [], "total": 0} @patch("ZoomMail.ZoomMailClient._http_request") def test_list_users_with_advanced_filters(self, mock_http_request): """Test listing users with additional filter options such as status, role, and license. When: - Advanced filters such as status, role ID, page number, and license type are provided. Then: - The function should make an HTTP request with these filters. - Verify the HTTP request is called with the expected parameters. - Assert the response matches the expected output with an empty user list. """ mock_http_request.return_value = {"users": [], "total": 0} advanced_filters = { "status": "inactive", "page_size": 50, "role_id": "admin", "page_number": "2", "include_fields": "email", "zoom_license": "pro", } response = self.client.list_users(**advanced_filters) expected_params = advanced_filters.copy() expected_params["license"] = expected_params.pop("zoom_license") expected_params["next_page_token"] = "" mock_http_request.assert_called_once_with(method="GET", url_suffix="/users", params=expected_params) assert response == {"users": [], "total": 0} class TestTheTestingModule(unittest.TestCase): def setUp(self): """Prepare environment for each test. Given: - An instance of ZoomMailClient is initialized with test credentials and default configurations. - Test parameters for API interaction are set up. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.params = { "url": "https://api.example.com", "client_id": "test_client_id", "client_secret": "test_client_secret", } @patch("ZoomMail.validate_params") @patch("ZoomMail.ZoomMailClient.obtain_access_token") def test_the_testing_module_success(self, mock_obtain_access_token, mock_validate_params): """Test if the module correctly handles successful authentication. When: - API credentials are validated successfully. Then: - The function should return 'ok' indicating successful authentication. - Ensure that the access token retrieval is invoked once. """ mock_validate_params.return_value = [] mock_obtain_access_token.return_value = {"success": True} result = the_testing_module(self.client, self.params) assert result == "ok" mock_obtain_access_token.assert_called_once() @patch("ZoomMail.validate_params") @patch("ZoomMail.ZoomMailClient.obtain_access_token") def test_the_testing_module_failed_authentication(self, mock_obtain_access_token, mock_validate_params): """Test handling of failed authentication due to invalid API credentials. When: - API responds with an error during authentication. Then: - The function should return a detailed error message. - Ensure that the result includes the specific error message from the API. """ mock_validate_params.return_value = [] mock_obtain_access_token.return_value = { "success": False, "error": "Invalid credentials", } result = the_testing_module(self.client, self.params) expected_error = "Errors were found while testing:\nInvalid credentials" assert result == expected_error @patch("ZoomMail.validate_params") @patch("ZoomMail.ZoomMailClient.obtain_access_token") def test_the_testing_module_invalid_params(self, mock_obtain_access_token, mock_validate_params): """Test the response when there are validation errors with the provided parameters. When: - Parameters are incomplete or improperly formatted. Then: - The function should return an error message listing all validation errors. """ mock_validate_params.return_value = ["URL parameter is missing."] mock_obtain_access_token.return_value = {"success": True} result = the_testing_module(self.client, self.params) expected_error = "Errors were found while testing:\nURL parameter is missing." assert result == expected_error class TestFetchIncidents(unittest.TestCase): def setUp(self): """ Prepare environment for each test. Given: - An instance of ZoomMailClient is initialized with test configurations. - Common parameters for fetching incidents are defined. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.params = { "default_mailbox": "default@example.com", "first_fetch": "3 days", "max_fetch": "50", } @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_no_emails_fetched(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test the scenario where no emails are fetched. When: - No new emails are available to fetch. Then: - Ensure no incidents are created and the last run is updated accordingly. """ mock_get_last_run.return_value = {} mock_thread_response = load_test_data("test_data/fetch/fetch_list_response_empty.json") self.client.list_emails = MagicMock(return_value=mock_thread_response) fetch_incidents(self.client, self.params) mock_set_last_run.assert_called_once() mock_incidents.assert_called_once() @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_emails_fetched(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test fetching emails successfully and creating incidents. When: - Emails are available to be fetched. Then: - Incidents should be created based on the fetched emails. """ mock_get_last_run.return_value = { "last_fetch_info": {"internalDate": 1622440000, "ids": []}, "next_page_token": "", } mock_list_response = load_test_data("test_data/fetch/fetch_list_response.json") self.client.list_emails = MagicMock(return_value=mock_list_response) mock_get_email_message_response_1 = load_test_data("test_data/fetch/fetch_email_1.json") mock_get_email_message_response_2 = load_test_data("test_data/fetch/fetch_email_2.json") mock_get_email_message_response_3 = load_test_data("test_data/fetch/fetch_email_3.json") self.client.get_email_message = MagicMock( side_effect=[ mock_get_email_message_response_1, mock_get_email_message_response_2, mock_get_email_message_response_3, ] ) fetch_incidents(self.client, self.params) incidents = mock_incidents.call_args[0][0] assert len(incidents) == 2 assert incidents[0]["name"] == "Zoom Encrypted Email" @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_handle_pagination(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test handling pagination during incident fetch. When: - There are more emails to fetch than can be retrieved in one API call. Then: - Pagination should be handled correctly, ensuring all emails are fetched across multiple API calls. """ mock_get_last_run.return_value = { "last_fetch_info": {"internalDate": 1622440000, "ids": []}, "next_page_token": "abc123", } self.client.list_emails = MagicMock( side_effect=[ { "messages": [{"id": "123", "threadId": "123"}], "nextPageToken": "abc123", }, {"messages": [{"id": "124", "threadId": "124"}]}, ] ) mock_get_email_message_response_1 = load_test_data("test_data/fetch/fetch_email_1.json") mock_get_email_message_response_2 = load_test_data("test_data/fetch/fetch_email_2.json") self.client.get_email_message = MagicMock( side_effect=[ mock_get_email_message_response_1, mock_get_email_message_response_2, ] ) fetch_incidents(self.client, self.params) # Verify if nextPageToken is handled correctly calls = mock_set_last_run.call_args_list expected_call = { "last_fetch_info": {"internalDate": 1622440000, "ids": []}, "next_page_token": "abc123", } # Check that setLastRun was called correctly assert len(calls) == 1 assert calls[0][0][0] == expected_call @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_deduplication_of_ids(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test deduplication of message IDs. When: - Fetched emails include IDs that are already in the last_fetch_info["ids"]. Then: - Ensure no duplicate incidents are created. """ mock_get_last_run.return_value = { "last_fetch_info": { "internalDate": 1622430000, "ids": ["d9e0967700000000_e8332447bb77d2cc_012", "d9e0967700000000_e83324610f26fd57_007"], }, "next_page_token": "", } mock_list_response = load_test_data("test_data/fetch/fetch_list_response.json") self.client.list_emails = MagicMock(return_value=mock_list_response) mock_get_email_message_response_1 = load_test_data("test_data/fetch/fetch_email_1.json") mock_get_email_message_response_2 = load_test_data("test_data/fetch/fetch_email_2.json") mock_get_email_message_response_3 = load_test_data("test_data/fetch/fetch_email_3.json") self.client.get_email_message = MagicMock( side_effect=[ mock_get_email_message_response_1, mock_get_email_message_response_2, mock_get_email_message_response_3, ] ) fetch_incidents(self.client, self.params) incidents = mock_incidents.call_args[0][0] assert len(incidents) == 2 # Ensure only new incidents are created @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_filtering_threads(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test filtering of threads when fetch_threads is false. When: - fetch_threads is false. - Emails are available to be fetched. Then: - Ensure only thread starter emails are fetched. """ self.params["fetch_threads"] = False mock_get_last_run.return_value = { "last_fetch_info": {"internalDate": 1622430000, "ids": []}, "next_page_token": "", } mock_list_response = load_test_data("test_data/fetch/fetch_list_response.json") self.client.list_emails = MagicMock(return_value=mock_list_response) mock_get_email_message_response_1 = load_test_data("test_data/fetch/fetch_email_1.json") mock_get_email_message_response_2 = load_test_data("test_data/fetch/fetch_email_2.json") mock_get_email_message_response_3 = load_test_data("test_data/fetch/fetch_email_3.json") self.client.get_email_message = MagicMock( side_effect=[ mock_get_email_message_response_1, mock_get_email_message_response_2, mock_get_email_message_response_3, ] ) fetch_incidents(self.client, self.params) incidents = mock_incidents.call_args[0][0] assert len(incidents) == 2 # Ensure only thread starter incidents are created @patch("ZoomMail.demisto.getLastRun") @patch("ZoomMail.demisto.setLastRun") @patch("ZoomMail.demisto.incidents") def test_not_filtering_threads(self, mock_incidents, mock_set_last_run, mock_get_last_run): """ Test not filtering of threads when fetch_threads is true. When: - fetch_threads is false. - Emails are available to be fetched. Then: - Ensure all emails are fetched regardless of being thread starters or not. """ self.params["fetch_threads"] = True mock_get_last_run.return_value = { "last_fetch_info": {"internalDate": 1, "ids": []}, "next_page_token": "", } mock_list_response = load_test_data("test_data/fetch/fetch_list_response.json") self.client.list_emails = MagicMock(return_value=mock_list_response) mock_get_email_message_response_1 = load_test_data("test_data/fetch/fetch_email_1.json") mock_get_email_message_response_2 = load_test_data("test_data/fetch/fetch_email_2.json") mock_get_email_message_response_3 = load_test_data("test_data/fetch/fetch_email_3.json") self.client.get_email_message = MagicMock( side_effect=[ mock_get_email_message_response_1, mock_get_email_message_response_2, mock_get_email_message_response_3, ] ) fetch_incidents(self.client, self.params) incidents = mock_incidents.call_args[0][0] assert len(incidents) == 3 # Ensure all emails are fetched class TestGetEmailThreadCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests by initializing a mock ZoomMailClient. Given: - An instance of ZoomMailClient is initialized with test configurations. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.args: dict[str, str] = { "email": "user@example.com", "thread_id": "1001", "format": "full", "metadata_headers": "Subject,Date", "max_results": "10", "page_token": "abc123", } @patch("ZoomMail.ZoomMailClient.get_email_thread") def test_successful_email_thread_retrieval(self, mock_get_email_thread): """ Test successful retrieval of an email thread. When: - Retrieving an email thread with valid parameters. Then: - Ensure the result is successfully parsed and contains expected thread information. """ test_data = load_test_data("test_data/thread/thread_list_response.json") mock_get_email_thread.return_value = test_data result = get_email_thread_command(self.client, self.args) assert isinstance(result, CommandResults) assert "Email Thread 1001" in result.readable_output assert "MYSTERY_GUID" in result.readable_output @patch("ZoomMail.ZoomMailClient.get_email_thread") def test_empty_email_thread(self, mock_get_email_thread): """ Test the retrieval of an email thread that has no messages. When: - The API returns an empty thread. Then: - Ensure the output indicates that no messages were found. """ test_data = load_test_data("test_data/thread/thread_list_response_empty.json") mock_get_email_thread.return_value = test_data result = get_email_thread_command(self.client, self.args) assert "has no messages" in result.readable_output @patch("ZoomMail.ZoomMailClient.get_email_thread") def test_email_thread_pagination_handling(self, mock_get_email_thread): """ Test proper handling of pagination tokens when retrieving email threads. When: - Pagination is required to retrieve all messages in a thread. Then: - Ensure pagination tokens are handled correctly and all messages are retrieved. """ mock_get_email_thread.return_value = { "messages": [{"id": "msg1"}, {"id": "msg2"}], "nextPageToken": "def456", } result = get_email_thread_command(self.client, self.args) mock_get_email_thread.assert_called_with("user@example.com", "1001", "full", "Subject,Date", "10", "abc123") assert "msg2" in result.readable_output def test_missing_thread_id_argument(self): """ Test the response when the required 'thread_id' argument is missing. When: - The 'thread_id' argument is not provided in the command arguments. Then: - Ensure a ValueError is raised indicating that both 'email' and 'thread_id' are required. """ self.args.pop("thread_id") with self.assertRaises(ValueError) as context: get_email_thread_command(self.client, self.args) assert "The 'thread_id' argument is required." in str(context.exception) class TestTrashEmailCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests, creating a mock client. Given: - An initialized ZoomMailClient with test configurations. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.args: dict[str, str] = { "email": "user@example.com", "message_id": "msg123", } @patch("ZoomMail.ZoomMailClient.trash_email") def test_successful_email_trashing(self, mock_trash_email): """ Test successful trashing of an email. When: - Trashing an email with a specific email address provided. Then: - Ensure the email is moved to TRASH and the result is successful. """ mock_trash_email.return_value = {"success": True, "id": "msg123"} result = trash_email_command(self.client, self.args) assert isinstance(result, CommandResults) assert "was moved to TRASH" in result.readable_output def test_missing_message_id_argument(self): """ Test response when required message_id argument is missing. When: - The 'message_id' argument is not provided in the command arguments. Then: - Ensure a ValueError is raised indicating that both 'email' and 'message_id' are required. """ self.args.pop("message_id") with self.assertRaises(ValueError) as context: trash_email_command(self.client, self.args) assert "The 'message_id' argument is required" in str(context.exception) class TestListEmailsCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests, creating a mock client. Given: - An initialized ZoomMailClient with test configurations. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.args: dict[str, str] = { "email": "user@example.com", "max_results": "100", "page_token": "", "label_ids": "INBOX,UNREAD", "query": "subject:urgent", "include_spam_trash": "false", } @patch("ZoomMail.ZoomMailClient.list_emails") def test_successful_email_listing(self, mock_list_emails): """ Test successful listing of emails. When: - Email listing is requested with specific filters. Then: - Verify the listing is successful and contains expected email messages. """ mock_list_emails.return_value = { "messages": [{"id": "msg1", "threadId": "thread1"}, {"id": "msg2", "threadId": "thread2"}] } result = list_emails_command(self.client, self.args) assert isinstance(result, CommandResults) assert "msg1" in result.readable_output assert "thread1" in result.readable_output @patch("ZoomMail.ZoomMailClient.list_emails") def test_no_emails_found(self, mock_list_emails): """ Test the scenario where no emails are found. When: - Email listing is requested but the API returns no messages. Then: - Verify the output indicates no messages were found. """ mock_list_emails.return_value = {} result = list_emails_command(self.client, self.args) assert "No messages found" in result.readable_output @patch("ZoomMail.ZoomMailClient.list_emails") def test_optional_parameters_handling(self, mock_list_emails): """ Test proper handling of optional parameters. When: - Email listing is requested with the 'include_spam_trash' set to True. Then: - Ensure the function is called with correct parameters, including the conversion of 'include_spam_trash' to a boolean. """ self.args["include_spam_trash"] = "true" mock_list_emails.return_value = {} list_emails_command(self.client, self.args) assert mock_list_emails.called called_with_kwargs = mock_list_emails.call_args[0] assert called_with_kwargs[5] is True # Checking 'include_spam_trash' boolean conversion class TestGetEmailAttachmentCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests, creating a mock client. Given: - An initialized ZoomMailClient with test configurations. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.args: dict[str, Any] = { "email": "user@example.com", "message_id": "1001", "attachment_id": "2001", } @patch("ZoomMail.ZoomMailClient.get_email_attachment") def test_successful_attachment_retrieval(self, mock_get_email_attachment): """ Test successful retrieval of an email attachment. When: - An attachment is requested from a specific email. Then: - Ensure the attachment is retrieved successfully and matches expected content. """ mock_get_email_attachment.return_value = { "data": "SGVsbG8sIHdvcmxkIQ==", # base64 for "Hello, world!" "attachmentId": "2001", } result = get_email_attachment_command(self.client, self.args) assert isinstance(result, CommandResults) assert "retrieved successfully" in result.readable_output assert result.outputs["attachmentId"] == "2001" @patch("ZoomMail.ZoomMailClient.get_email_attachment") def test_no_attachment_found(self, mock_get_email_attachment): """ Test the scenario where no attachment is found. When: - An attachment is requested but the API returns no data. Then: - Ensure the output indicates that no data was found. """ mock_get_email_attachment.return_value = {} result = get_email_attachment_command(self.client, self.args) assert "No data found" in result.readable_output def test_missing_required_arguments(self): """ Test response when required arguments are missing. When: - The command is executed with missing 'message_id' or 'attachment_id'. Then: - Ensure a ValueError is raised indicating that both 'message_id' and 'attachment_id' are required. """ # Missing message_id with self.assertRaises(ValueError) as context: missing_args = self.args.copy() del missing_args["message_id"] get_email_attachment_command(self.client, missing_args) assert "The 'message_id', and 'attachment_id' arguments are required" in str(context.exception) # Missing attachment_id with self.assertRaises(ValueError) as context: missing_args = self.args.copy() del missing_args["attachment_id"] get_email_attachment_command(self.client, missing_args) assert "The 'message_id', and 'attachment_id' arguments are required" in str(context.exception) class TestGetMailboxProfileCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests, creating a mock client. Given: - An initialized ZoomMailClient with test configurations. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.args = {"email": "user@example.com"} @patch("ZoomMail.ZoomMailClient.get_mailbox_profile") def test_successful_mailbox_profile_retrieval(self, mock_get_mailbox_profile): """ Test successful retrieval of a mailbox profile. When: - A mailbox profile retrieval command is executed with a valid email. Then: - Verify the command retrieves and displays the mailbox profile as expected. """ # Mock API response mock_get_mailbox_profile.return_value = { "emailAddress": "user@example.com", "groupEmails": ["group1@example.com", "group2@example.com"], "createTime": 1588300800, # Example timestamp "status": "active", "mboxSize": 2048, "messagesTotal": 100, "threadsTotal": 10, "encryptionEnabled": True, "labelEncryptionEnabled": False, "historyId": "12345", } # Execute command result = get_mailbox_profile_command(self.client, self.args) # Verify results assert isinstance(result, CommandResults) assert "Mailbox Profile for user@example.com" in result.readable_output assert "active" in result.readable_output assert "2048 bytes" in result.readable_output class TestListUsersCommand(unittest.TestCase): def setUp(self): """ Prepare environment for tests, creating a mock client. Given: - An initialized ZoomMailClient with configuration for API connection. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.args = { "status": "active", "limit": "50", "role_id": "admin", "page_number": "1", "include_fields": "email,status", "next_page_token": "", "license": "pro", } @patch("ZoomMail.ZoomMailClient.list_users") def test_successful_user_listing(self, mock_list_users): """ Test successful listing of users. When: - Requesting to list users with specific filter settings. Then: - Ensure the users are listed as expected and verify key user information is displayed. """ mock_list_users.return_value = { "users": [ {"email": "user1@example.com", "first_name": "John", "last_name": "Doe", "type": "admin", "status": "active"}, { "email": "user2@example.com", "first_name": "Jane", "last_name": "Smith", "type": "member", "status": "inactive", }, ] } result = list_users_command(self.client, self.args) assert isinstance(result, CommandResults) assert "user1@example.com" in result.readable_output assert "John" in result.readable_output @patch("ZoomMail.ZoomMailClient.list_users") def test_no_users_found(self, mock_list_users): """ Test the scenario where no users are found. When: - Requesting to list users but the response contains no user data. Then: - Ensure the output indicates no users were found. """ mock_list_users.return_value = {"users": []} result = list_users_command(self.client, self.args) assert "No entries" in result.readable_output @patch("ZoomMail.ZoomMailClient.list_users") def test_user_listing_with_pagination(self, mock_list_users): """ Test user listing with pagination. When: - Requesting to list users with a pagination token to fetch additional pages. Then: - Ensure pagination is handled correctly and subsequent user data is retrieved. """ mock_list_users.return_value = { "users": [ {"email": "user3@example.com", "first_name": "Alice", "last_name": "Johnson", "type": "admin", "status": "active"} ], "nextPageToken": "abc123", } self.args["next_page_token"] = "abc123" result = list_users_command(self.client, self.args) mock_list_users.assert_called_with("active", 50, "admin", "1", "email,status", "", "pro") assert "Alice" in result.readable_output class TestProcessAttachments(unittest.TestCase): def setUp(self): """ Prepare test environment. Given: - An initialized ZoomMailClient with configuration for API connection. - Mocked email details including attachments to process. """ self.client = ZoomMailClient( base_url="https://api.example.com", client_id="test_client_id", client_secret="test_client_secret", account_id="test_account_id", default_email="default@example.com", ) self.client.access_token = "TestToken" self.email = "test@example.com" self.message = { "id": "123", "attachments": [ {"ID": "attach1", "Name": "file1.pdf"}, {"ID": "attach2", "Name": "file2.pdf"}, ], } @patch("ZoomMail.demisto.error") @patch("ZoomMail.ZoomMailClient.get_email_attachment") def test_process_attachments_failure(self, mock_get_email_attachment, mock_demisto_error): """ Test handling errors while processing attachments. When: - Retrieval of an attachment fails due to an API error. Then: - No attachments are processed and an error is logged. """ mock_get_email_attachment.side_effect = Exception("Failed to retrieve attachment") result = process_attachments(self.message, self.client, self.email) assert len(result) == 0 # Verify no attachments processed mock_demisto_error.assert_called() # Error handling should be invoked @patch("ZoomMail.demisto.error") @patch("ZoomMail.ZoomMailClient.get_email_attachment") def test_process_attachments_success(self, mock_get_email_attachment, mock_demisto_error): """ Test successful processing of email attachments. When: - Attachment data is retrieved successfully. Then: - Attachments are processed and added to the result without any errors. """ encoded_data = base64.urlsafe_b64encode(b"Test file content").decode("ascii") mock_get_email_attachment.return_value = {"data": encoded_data} result = process_attachments(self.message, self.client, self.email) assert len(result) == 2 # Ensure both attachments are processed assert result[0]["name"] == "file1.pdf" assert result[1]["name"] == "file2.pdf" mock_demisto_error.assert_not_called() # No error should be logged class TestCreateEmailMessage(unittest.TestCase): def setUp(self): """ Given: - An email configuration including sender, recipient, subject, body text, HTML content, and attachment IDs. Prepare environment for tests, setting up the necessary email components. """ self.from_email = "from@example.com" self.to_email = "to@example.com" self.subject = "Test Subject" self.body_text = "This is a plain text body." self.html_text = "<p>This is an HTML body.</p>" self.attachment_ids = ["attach1", "attach2"] @patch("ZoomMail.attach_files_to_email") def test_create_plain_text_email(self, mock_attach_files): """ When: - Creating a plain text email without HTML or attachments. Then: - Verify the email object is correctly formed with the appropriate headers and body. """ email = create_email_message(self.from_email, self.to_email, self.subject, self.body_text, None, []) assert isinstance(email, MIMEMultipart) assert mock_attach_files.called assert email["From"] == self.from_email assert email["To"] == self.to_email assert email["Subject"] == self.subject assert len(email.get_payload()) == 1 assert isinstance(email.get_payload()[0], MIMEText) assert email.get_payload()[0].get_payload() == self.body_text @patch("ZoomMail.attach_files_to_email") def test_create_html_email(self, mock_attach_files): """ When: - Creating an email with HTML content only. Then: - Verify the email object is correctly formed with HTML content. """ email = create_email_message(self.from_email, self.to_email, self.subject, None, self.html_text, []) assert isinstance(email, MIMEMultipart) assert mock_attach_files.called assert len(email.get_payload()) == 1 assert isinstance(email.get_payload()[0], MIMEText) assert email.get_payload()[0].get_payload() == self.html_text @patch("ZoomMail.attach_files_to_email") def test_create_email_with_attachments(self, mock_attach_files): """ When: - Creating an email with both text, HTML content, and attachments. Then: - Verify the email object includes all parts and attachments are handled correctly. """ email = create_email_message( self.from_email, self.to_email, self.subject, self.body_text, self.html_text, self.attachment_ids, ) assert isinstance(email, MIMEMultipart) assert mock_attach_files.called assert mock_attach_files.call_args[0][1] == self.attachment_ids @patch("ZoomMail.attach_files_to_email") def test_create_email_all_elements(self, mock_attach_files): """ When: - Creating an email with all elements: plain text, HTML, and attachments. Then: - Verify the multipart email is correctly constructed with multiple payloads. """ email = create_email_message( self.from_email, self.to_email, self.subject, self.body_text, self.html_text, self.attachment_ids, ) assert isinstance(email, MIMEMultipart) assert mock_attach_files.called assert len(email.get_payload()) == 2 # Expecting two MIMEText parts (plain and html) assert any(part.get_content_type() == "text/plain" for part in email.get_payload()) assert any(part.get_content_type() == "text/html" for part in email.get_payload()) class TestAttachFilesToEmail(unittest.TestCase): def setUp(self): """ Given: - A MIMEMultipart email message and a list of attachment IDs. Prepare environment for testing file attachment functionality. """ self.message = MIMEMultipart() self.attachment_ids = ["123", "456", "789"] @patch("ZoomMail.demisto.getFilePath") @patch("ZoomMail.attach_file") def test_attach_files_success(self, mock_attach_file, mock_get_file_path): """ When: - Attaching files to an email, and all files are found. Then: - Verify that all files are attached successfully and the correct methods are called. """ mock_get_file_path.side_effect = [ {"path": "/path/to/file1", "name": "file1.txt"}, {"path": "/path/to/file2", "name": "file2.txt"}, {"path": "/path/to/file3", "name": "file3.txt"}, ] attach_files_to_email(self.message, self.attachment_ids) assert mock_attach_file.call_count == 3 mock_attach_file.assert_any_call(self.message, "/path/to/file1", "file1.txt") mock_attach_file.assert_any_call(self.message, "/path/to/file2", "file2.txt") mock_attach_file.assert_any_call(self.message, "/path/to/file3", "file3.txt") @patch("ZoomMail.demisto.getFilePath") @patch("ZoomMail.attach_file") def test_attach_files_no_files_found(self, mock_attach_file, mock_get_file_path): """ When: - Attempting to attach files, but no files are found. Then: - Verify that no files are attached and attach_file method is not called. """ mock_get_file_path.return_value = None attach_files_to_email(self.message, self.attachment_ids) mock_attach_file.assert_not_called() @patch("ZoomMail.demisto.getFilePath") @patch("ZoomMail.attach_file") def test_attach_files_partial_files_found(self, mock_attach_file, mock_get_file_path): """ When: - Attempting to attach files, but only some of the files are found. Then: - Verify that only the found files are attached and the attach_file method is called correctly for those files. """ mock_get_file_path.side_effect = [ {"path": "/path/to/file1", "name": "file1.txt"}, None, {"path": "/path/to/file3", "name": "file3.txt"}, ] attach_files_to_email(self.message, self.attachment_ids) assert mock_attach_file.call_count == 2 mock_attach_file.assert_any_call(self.message, "/path/to/file1", "file1.txt") mock_attach_file.assert_any_call(self.message, "/path/to/file3", "file3.txt") class TestAttachFile(unittest.TestCase): def setUp(self): """ Given: - A new MIMEMultipart message for testing attachment functionalities. Prepare environment for testing the attachment of a single file to an email message. """ self.message = MIMEMultipart() @patch("builtins.open", new_callable=unittest.mock.mock_open, read_data="file content") @patch("ZoomMail.MIMEBase") @patch("ZoomMail.encoders.encode_base64") def test_attach_file(self, mock_encode_base64, mock_mime_base, mock_open): """ When: - Attaching a file to an email message using the attach_file function. Then: - Ensure the file is opened, read, and encoded correctly. - Verify the MIME part is created with appropriate headers and attached to the message. """ # Configure the MIMEBase mock to simulate file attachment behavior. mock_part = MagicMock(spec=MIMEBase) mock_mime_base.return_value = mock_part attach_file(self.message, "/fake/path/to/file.txt", "file.txt") # Validate file opening and content handling. mock_open.assert_called_once_with("/fake/path/to/file.txt", "rb") mock_mime_base.assert_called_once_with("application", "octet-stream") mock_part.set_payload.assert_called_once_with("file content") mock_encode_base64.assert_called_once_with(mock_part) mock_part.add_header.assert_called_once_with("Content-Disposition", "attachment", filename="file.txt") # Confirm the MIME part is correctly attached to the MIMEMultipart message. assert len(self.message.get_payload()) == 1 class TestMainFunction(unittest.TestCase): @patch("ZoomMail.demisto") def test_command_routing(self, mock_demisto): """ Given: - Command name 'test-module' and necessary parameters and arguments are set in the demisto mock. When: - main function is called. Then: - Ensure that the correct function for 'test-module' is called and returns the expected result. """ # Setup mocks for demisto functions and client methods mock_demisto.params.return_value = { "url": "https://api.example.com", "credentials": {"identifier": "id", "password": "pass"}, "account_id": "12345", "insecure": False, "proxy": False, "default_email": "default@example.com", } mock_demisto.args.return_value = {} mock_demisto.command.return_value = "test-module" # Prepare a realistic return value for the command function being tested mock_command_function = MagicMock(return_value="ok") # Patch the specific function within the COMMAND_FUNCTIONS dict with patch("ZoomMail.the_testing_module", mock_command_function): main() # Ensure the mock was called (i.e., routing works correctly) mock_command_function.assert_called_once() # Check that the return value is correct assert mock_command_function.return_value == "ok" @patch("ZoomMail.demisto") def test_handle_not_implemented_command(self, mock_demisto): """ Given: - A command 'non-existent-command' that is not implemented. When: - main function is called. Then: - An error is raised indicating the command is not implemented. """ mock_demisto.command.return_value = "non-existent-command" with self.assertRaises(NotImplementedError): main() @patch("ZoomMail.demisto") def test_error_handling(self, mock_demisto): """ Given: - An exception is expected to be thrown by the 'test-module' command. When: - main function is called. Then: - The exception is handled properly and the error message is processed. """ mock_demisto.command.return_value = "test-module" mock_demisto.params.return_value = {} mock_demisto.args.return_value = {} # Simulate an exception in the command function with ( patch("ZoomMail.the_testing_module", side_effect=DemistoException("Error")), patch("ZoomMail.return_error") as mock_return_error, ): main() mock_return_error.assert_called_once() class TestBase64Decoding(unittest.TestCase): def test_decode_base64_normal(self): # Test with a normal base64 encoded string encoded = base64.b64encode(b"hello world").decode("utf-8") result = decode_base64(encoded) assert result == "hello world" def test_decode_base64_incorrect_padding(self): # Test base64 string with incorrect padding encoded = base64.b64encode(b"hello world").decode("utf-8").rstrip("=") result = decode_base64(encoded) assert result == "hello world" def test_decode_base64_invalid_characters(self): # Test base64 string with invalid characters encoded = "aGVsbG8gd29ybGQ$" result = decode_base64(encoded) assert result == "hello world" def test_decode_base64_special_characters(self): # Test base64 string with special URL characters encoded = base64.urlsafe_b64encode(b"hello?world!").decode("utf-8") result = decode_base64(encoded) assert result == "hello?world!" def test_decode_base64_empty_string(self): # Test with an empty string result = decode_base64("") assert result == "" def test_correct_base64_errors(self): # Test the correction of non-base64 characters encoded = "aGVsbG8gd29ybGQ$==" corrected = correct_base64_errors(encoded) assert set(corrected) <= set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") assert len(corrected) % 4 == 0 def test_safe_bytes_to_string(self): # Test converting bytes to string safely byte_data = b"hello world" result = safe_bytes_to_string(byte_data) assert result == "hello world" def test_safe_bytes_to_string_decoding_error(self): # Test safe byte to string conversion with non-utf8 encodable bytes byte_data = bytes([0xFF, 0xFE, 0xFD]) result = safe_bytes_to_string(byte_data) assert result == "" if __name__ == "__main__": unittest.main()