Mail Listener v2

Listens to a mailbox and enables incident triggering via e-mail.

Messaging and Conferencing · Mail Listener

Details

IDMail Listener v2
ProviderOpen Source
CategoryMessaging and Conferencing
From Version5.0.0
Docker Imagedemisto/py3-tools:1.0.0.10120494
Supported ModulesAgentix XSIAM

README

Overview


Listens to a mailbox and enables incident triggering via e-mail.


Configure Mail Listener v2 in Cortex

Parameter Description Required
Mail Server Hostname / IP address   True
IMAP Port Default is 143. True
Username   True
Incoming mail folder Default is INBOX. True
Fetch incidents   False
Incident type   False
Fetch mails from these senders addresses only (eg. admin@demo.com,test@demo.com)   False
Fetch mails from these domains only (eg. demo.com, test.com)   False
Query search with headers Whether to query search with headers when filtering by sender’s email address. Default is false. False
First fetch time Default is 1 hour. False
The maximum number of incidents to fetch each time Default is 50. False
Delete processed emails   False
Include raw body in incidents   False
Save the email .eml file   False
Fetch by email date Whether to fetch by email date. Default is true. False
Use TLS for connection (defaults to True) Default is true. False
Trust any certificate (not secure)   False
Client Certificate And Private Key (List of PEMs)   False

Commands

mail-listener-list-emails


Fetch mails according to the configuration

Base Command

mail-listener-list-emails

Input

There are no input arguments for this command.

Context Output

Path Type Description
MailListener.EmailPreview.Subject String The subject of the mail
MailListener.EmailPreview.Date Date The date when the mail was received.
MailListener.EmailPreview.To String The recipient of the mail.
MailListener.EmailPreview.From String The sender of the mail.
MailListener.EmailPreview.ID string The ID of the mail.

Command Example


#### Context Example

{
“MailListener”: {
“EmailPreview”: {
“Date”: “2020-08-12T11:13:35+00:00”,
“From”: “test@demistodev.com”,
“ID”: 65445,
“Subject”: “foooSubject”,
“To”: [
“test@demistodev.com”
]
}
}
}


#### Human Readable Output

>### Results
>
>|Date|From|ID|Subject|To|
>|---|---|---|---|---|
>| 2020-08-12T11:13:35+00:00 | test@demistodev.com | 65445 | foooSubject | test@demistodev.com |

### mail-listener-get-email

***
Fetches an email by email ID

#### Base Command

`mail-listener-get-email`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| message-id | Message ID as fetched in 'mail-listener-list-emails' command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| MailListener.Email.to | String | The recipients of the mail. |
| MailListener.Email.cc | String | The mail's cc. |
| MailListener.Email.bcc | String | The mail's bcc. |
| MailListener.Email.from | String | The sender of the mail. |
| MailListener.Email.format | String | The format of the mail. |
| MailListener.Email.text | String | The plain text of the mail. |
| MailListener.Email.HTML | String | The HTML display of the mail if exists. |
| MailListener.Email.subject | String | The subject of the mail. |
| MailListener.Email.attachments | String | The attachments of the mail if exists. |
| MailListener.Email.headers | String | The headers of the mail. |

#### Command Example

```!mail-listener-get-email message-id=65445```

#### Context Example

{
“MailListener”: {
“Email”: {
“attachments”: [],
“bcc”: “”,
“cc”: “”,
“format”: “text/plain”,
“from”: “test@demistodev.com”,
“headers”: {
“Content-Transfer-Encoding”: “quoted-printable”,
“Content-Type”: “text/plain; charset=UTF-8”,
“Date”: “Wed, 12 Aug 2020 11:13:35 +0000”,
“From”: “test@demistodev.com”,
“Message-ID”: “5f33cedf.1c69fb81.e5562.38a5@mx.google.com”,
“Mime-Version”: “1.0”,
“Received”: “from localhost (13.100.68.34.bc.googleusercontent.com. [192.0.0.1])\r\n by smtp.gmail.com with ESMTPSA id t5sm917197ilp.15.2020.08.12.04.13.35\r\n for test@demistodev.com\r\n (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\r\n Wed, 12 Aug 2020 04:13:35 -0700 (PDT)”,
“Return-Path”: “test@demistodev.com”,
“Subject”: “foooSubject”,
“To”: “test@demistodev.com”,
“X-Google-Original-From”: “koko@demisto.com”
},
“rawHeaders”: “Return-Path: test@demistodev.com\nReceived: from localhost (13.100.68.34.bc.googleusercontent.com. [34.68.100.13])\r\n by smtp.gmail.com with ESMTPSA id t5sm917197ilp.15.2020.08.12.04.13.35\r\n for test@demistodev.com\r\n (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\r\n Wed, 12 Aug 2020 04:13:35 -0700 (PDT)\nMessage-ID: 5f33cedf.1c69fb81.e5562.38a5@mx.google.com\nFrom: test@demistodev.com\nX-Google-Original-From: koko@demisto.com\nMime-Version: 1.0\nDate: Wed, 12 Aug 2020 11:13:35 +0000\nTo: test@demistodev.com\nSubject: foooSubject\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable”,
“subject”: “foooSubject”,
“text”: “foooBody”,
“to”: “test@demistodev.com”
}
}
}


#### Human Readable Output

>### Results
>
>|attachments|bcc|cc|format|from|headers|rawHeaders|subject|text|to|
>|---|---|---|---|---|---|---|---|---|---|
>|  |  |  | text/plain | test@demistodev.com | Return-Path: <test@demistodev.com><br/>Received: from localhost (13.100.68.34.bc.googleusercontent.com. [34.68.100.13])<br/>        by smtp.gmail.com with ESMTPSA id t5sm917197ilp.15.2020.08.12.04.13.35<br/>        for <test@demistodev.com><br/>        (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);<br/>        Wed, 12 Aug 2020 04:13:35 -0700 (PDT)<br/>Message-ID: <5f33cedf.1c69fb81.e5562.38a5@mx.google.com><br/>From: test@demistodev.com<br/>X-Google-Original-From: koko@demisto.com<br/>Mime-Version: 1.0<br/>Date: Wed, 12 Aug 2020 11:13:35 +0000<br/>To: test@demistodev.com<br/>Subject: foooSubject<br/>Content-Type: text/plain; charset=UTF-8<br/>Content-Transfer-Encoding: quoted-printable | Return-Path: <test@demistodev.com><br/>Received: from localhost (13.100.68.34.bc.googleusercontent.com. [34.68.100.13])<br/>        by smtp.gmail.com with ESMTPSA id t5sm917197ilp.15.2020.08.12.04.13.35<br/>        for <test@demistodev.com><br/>        (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);<br/>        Wed, 12 Aug 2020 04:13:35 -0700 (PDT)<br/>Message-ID: <5f33cedf.1c69fb81.e5562.38a5@mx.google.com><br/>From: test@demistodev.com<br/>X-Google-Original-From: koko@demisto.com<br/>Mime-Version: 1.0<br/>Date: Wed, 12 Aug 2020 11:13:35 +0000<br/>To: test@demistodev.com<br/>Subject: foooSubject<br/>Content-Type: text/plain; charset=UTF-8<br/>Content-Transfer-Encoding: quoted-printable | foooSubject | foooBody | test@demistodev.com |

### mail-listener-get-email-as-eml

***
Fetches an email by message ID and download it's eml file

#### Base Command

`mail-listener-get-email-as-eml`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| message-id | Message ID as fetched in 'mail-listener-list-emails' command. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!mail-listener-get-email-as-eml message-id=65445```

#### Context Example

{
“File”: {
“EntryID”: “1276@97a9b81e-928e-4c93-80bc-2729ca35cb1c”,
“Extension”: “eml”,
“Info”: “message/rfc822”,
“MD5”: “4411c30b672dd8fee62d332c970e07bb”,
“Name”: “original-email-file.eml”,
“SHA1”: “1713dc8369f89bc1c3f665eeffc3a2b7de4c2f7b”,
“SHA256”: “d6e145175a9abd9b51f3af71a6a4971ec922888addd2f96cdd484c52ff6fcb43”,
“SHA512”: “0b4af9cd6899e15fcbb3fbdde4592ccd44f681769c554afd437214984cdc254923167563e6d7b763bb96cc3de40f684e9852ec2617ba90efdf6d9342564dddfd”,
“SSDeep”: “12:k11sxpBGVTdLS4NuFWsRqzR2kAmM4YTxXX6oFTrYQWptR9zHxXARx2mi7xoQFQ0f:SydGVTdLS4cJGRzCT96odcpHR2x2milL”,
“Size”: 680,
“Type”: “SMTP mail text, ASCII text, with CRLF line terminators”
}
}
```

Additional Information

  • In the first fetch iteration, some emails may be skipped and not fetched as incidents - the integration will fetch just the last available emails for the given day, as set in The maximum number of incidents to fetch each time and First fetch time. This behavior is due to the fact that IMAP time filter is limited to day based filter.

    Subsequent fetch iterations should fetch emails as they are received, without further issue.

  • The occurred time for emails with no received date field will be the time it was fetched to the system.

Configuration parameters

  • MailServerURL — Mail Server Hostname / IP address (required)
  • port — IMAP Port (required)
  • credentials — Username (required)
  • clientCertAndKey
  • folder — Incoming mail folder (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • permittedFromAdd — Fetch mails from these senders addresses only (eg. admin@demo.com,test@demo.com)
  • permittedFromDomain — Fetch mails from these domains only (eg. demo.com, test.com)
  • with_headers — Query search with headers
  • first_fetch — First fetch time
  • limit — The maximum number of incidents to fetch each time
  • delete_processed — Delete processed emails
  • Include_raw_body — Include raw body in incidents
  • save_file — Save the email .eml file
  • date_fetch — Fetch by email date
  • TLS_connection — Use TLS for connection (defaults to True)
  • insecure — Trust any certificate (not secure)

Commands (3)

  • mail-listener-get-email

    Retrieves an email by email ID.

  • mail-listener-get-email-as-eml

    Fetches an email by message ID and returns the information in an eml file format.

  • mail-listener-list-emails

    Retrieves emails according to the configuration.

from datetime import datetime, UTC
from freezegun import freeze_time
import pytest

MAIL_STRING = rb"""Delivered-To: to@test1.com
MIME-Version: 1.0
From: John Smith <from@test1.com>
Date: Mon, 10 Aug 2020 10:17:16 +0300
Subject: Testing email for mail listener
To: to@test1.com
Content-Type: multipart/alternative; boundary="0000000000002b271405ac80bf8b"


--0000000000002b271405ac80bf8b
Content-Type: text/plain; charset="UTF-8"



--0000000000002b271405ac80bf8b
Content-Type: text/html; charset="UTF-8"

<div dir="ltr"><br></div>
<p>C:\Users</p>
<p>C:\\Users</p>

--0000000000002b271405ac80bf8b--
"""

MAIL_STRING_NO_DATE = rb"""Delivered-To: to@test1.com
MIME-Version: 1.0
From: John Smith <from@test1.com>
Date:
Subject: Testing email for mail listener
To: to@test1.com
Content-Type: multipart/alternative; boundary="0000000000002b271405ac80bf8b"


--0000000000002b271405ac80bf8b
Content-Type: text/plain; charset="UTF-8"



--0000000000002b271405ac80bf8b
Content-Type: text/html; charset="UTF-8"

<div dir="ltr"><br></div>
<p>C:\Users</p>
<p>C:\\Users</p>

--0000000000002b271405ac80bf8b--
"""

MAIL_STRING_NOT_BYTES = r"""Delivered-To: to@test1.com
MIME-Version: 1.0
From: John Smith <from@test1.com>
Date: Mon, 10 Aug 2020 10:17:16 +0300
Subject: Testing email for mail listener
To: to@test1.com
Content-Type: multipart/alternative; boundary="0000000000002b271405ac80bf8b"


--0000000000002b271405ac80bf8b
Content-Type: text/plain; charset="UTF-8"



--0000000000002b271405ac80bf8b
Content-Type: text/html; charset="UTF-8"

<div dir="ltr"><br></div>
<p>C:\Users</p>

--0000000000002b271405ac80bf8b--
"""
EXPECTED_LABELS = [
    {"type": "Email/from", "value": "from@test1.com"},
    {"type": "Email/format", "value": "multipart/alternative"},
    {"type": "Email/text", "value": ""},
    {"type": "Email/subject", "value": "Testing email for mail listener"},
    {"type": "Email/headers/Delivered-To", "value": "to@test1.com"},
    {"type": "Email/headers/MIME-Version", "value": "1.0"},
    {"type": "Email/headers/From", "value": "John Smith <from@test1.com>"},
    {"type": "Email/headers/Date", "value": "Mon, 10 Aug 2020 10:17:16 +0300"},
    {"type": "Email/headers/Subject", "value": "Testing email for mail listener"},
    {"type": "Email/headers/To", "value": "to@test1.com"},
    {"type": "Email/headers/Content-Type", "value": 'multipart/alternative; boundary="0000000000002b271405ac80bf8b"'},
    {"type": "Email", "value": "to@test1.com"},
    {"type": "Email/html", "value": '<div dir="ltr"><br></div>\n<p>C:\\Users</p>\n<p>C:\\\\Users</p>'},
]


@freeze_time("2022-12-11 13:40:00 UTC")
@pytest.mark.parametrize(
    "mail_string, mail_date", [(MAIL_STRING, "2020-08-10T07:17:16+00:00"), (MAIL_STRING_NO_DATE, "2022-12-11T13:40:00+00:00")]
)
def test_convert_to_incident(mail_string, mail_date):
    """
    Given:
        - Bytes representation of a mail

        When:
        - Parsing it to incidents

        Then:
        - Validate the 'attachments', 'occurred', 'details' and 'name' fields are parsed as expected
    """
    from MailListenerV2 import Email

    email = Email(mail_string, False, False, 0)
    incident = email.convert_to_incident()
    assert incident["occurred"] == mail_date
    assert incident["details"] == email.text or email.html
    assert incident["name"] == email.subject


@pytest.mark.parametrize(
    "time_to_fetch_from, with_header, permitted_from_addresses, permitted_from_domains, uid_to_fetch_from, expected_query",
    # noqa: E501
    [
        (
            datetime(year=2020, month=10, day=1),  # noqa: E126
            False,  # noqa: E126
            ["test1@mail.com", "test2@mail.com"],
            ["test1.com", "domain2.com"],
            4,
            [
                "OR",
                "OR",
                "OR",
                "FROM",
                "domain2.com",
                "FROM",
                "test1.com",
                "FROM",
                "test1@mail.com",
                "FROM",
                "test2@mail.com",
                "SINCE",
                datetime(year=2020, month=10, day=1),
                "UID",
                "4:*",
            ],
        ),
        (
            datetime(year=2020, month=10, day=1),  # noqa: E126
            True,  # noqa: E126
            ["test1@mail.com", "test2@mail.com"],
            ["test1.com", "domain2.com"],
            4,
            [
                "OR",
                "OR",
                "OR",
                "HEADER",
                "FROM",
                "domain2.com",
                "HEADER",
                "FROM",
                "test1.com",
                "HEADER",
                "FROM",
                "test1@mail.com",
                "HEADER",
                "FROM",
                "test2@mail.com",
                "SINCE",
                datetime(year=2020, month=10, day=1),
                "UID",
                "4:*",
            ],
        ),
        (
            None,  # noqa: E126
            "",  # noqa: E126
            [],
            [],
            1,
            ["UID", "1:*"],
        ),
    ],
)
def test_generate_search_query(
    time_to_fetch_from, with_header, permitted_from_addresses, permitted_from_domains, uid_to_fetch_from, expected_query
):
    """
    Given:
        - The date from which mails should be queried
        - A list of email addresses from which mails should be queried
        - A list of domains from which mails should be queried

        When:
        - Generating search query from these arguments

        Then:
        - Validate the search query as enough 'OR's in the beginning (Σ(from n=0to(len(addresses)+len(domains)))s^(n-1))
        - Validate the search query has FROM before each address or domain
        - Validate query has SINCE before the datetime object
    """
    from MailListenerV2 import generate_search_query

    assert (
        generate_search_query(
            time_to_fetch_from, with_header, permitted_from_addresses, permitted_from_domains, uid_to_fetch_from
        )
        == expected_query
    )


def test_generate_labels():
    """
    Given:
    - Bytes representation of a mail

    When:
    - Generating mail labels

    Then:
    - Validate all expected labels are in the generated labels, and are from type string
    """
    from MailListenerV2 import Email

    email = Email(MAIL_STRING, False, False, 0)
    labels = email._generate_labels()
    for label in EXPECTED_LABELS:
        assert label in labels, f"Label {label} was not found in the generated labels, {labels}"


def mock_email():
    from unittest.mock import patch
    from MailListenerV2 import Email

    with patch.object(Email, "__init__", lambda a, b, c, d, e: None):
        email = Email("data", False, False, 0)
        email.id = 0
        email.date = datetime(year=2020, month=10, day=1, tzinfo=UTC)
        return email


def mock_email_without_date(mail_id=0):
    """Build a mock Email whose Date header could not be parsed, so its date is None."""
    from unittest.mock import patch
    from MailListenerV2 import Email

    with patch.object(Email, "__init__", lambda a, b, c, d, e: None):
        email = Email("data", False, False, 0)
        email.id = mail_id
        email.date = None
        return email


@pytest.mark.parametrize(
    "src_data, expected", [({1: {b"RFC822": rb"C:\User\u"}}, rb"C:\User\u"), ({2: {b"RFC822": rb"C:\User\u"}}, rb"C:\User\u")]
)
def test_fetch_mail_gets_bytes(mocker, src_data, expected):
    """
    Given:
        A byte string representing response from API
    When:
        1. The string returns as string
        2. The string returns as bytes
    Then:
        validates Email is called with a bytes string.
    """
    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mail_mocker = mocker.patch("MailListenerV2.Email", return_value=mock_email())
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(IMAPClient, "search", return_value=[1])
    mocker.patch.object(IMAPClient, "fetch", return_value=src_data)
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    fetch_mails(IMAPClient("http://example_url.com"))
    assert mail_mocker.call_args[0][0] == expected


def test_fetch_mail__default_uid(
    mocker,
):
    """
    Given:
        - No uid is passed to fetch_mails function
    When:
        - Fetching mails
    Then:
        - Validate search query is empty
    """
    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mocker.patch("MailListenerV2.Email")
    mocker.patch.object(demisto, "debug")
    search_mocker = mocker.patch.object(IMAPClient, "search", return_value=[1])
    mocker.patch.object(IMAPClient, "fetch")
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    fetch_mails(IMAPClient("http://example_url.com"))
    assert search_mocker.call_args[0][0] == []  # default uid is 0 so no search query is passed


def test_invalid_mail_object_handling(mocker):
    """
    Given:
        - Fetch response with 3 mails, the 2nd being invalid mail
    When:
        - Fetching mails
    Then:
        - Validate only 2 valid mails are returned
        - Validate skipping invalid mail and printing relevant debug message
    """
    src_data = {1: {b"RFC822": rb"C:\User1\u"}, 2: {b"RFC822": rb"C:\User2\u"}, 3: {b"RFC822": rb"C:\User3\u"}}

    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mock_email_1 = mock_email()
    mock_email_3 = mock_email()
    mock_email_1.id, mock_email_3.id = 10, 11

    mocker.patch("MailListenerV2.Email", side_effect=[mock_email_1, Exception("Invalid Mail"), mock_email_3])
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(IMAPClient, "search", return_value=[1])
    mocker.patch.object(IMAPClient, "fetch", return_value=src_data)
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    mails_fetched, messages_fetched, _ = fetch_mails(IMAPClient("http://example_url.com"))
    assert len(mails_fetched) == 2
    assert messages_fetched == [10, 11]


def test_get_eml_attachments():
    from MailListenerV2 import Email
    import email

    # Test an email with a PNG attachment
    with open("test_data/eml-with-jpeg.eml", "rb") as f:
        msg = email.message_from_bytes(f.read())
    res = Email.get_eml_attachments(msg.as_bytes())
    assert res == []
    # Test an email with EML attachment
    with open("test_data/eml-with-eml-with-attachment.eml", "rb") as f:
        msg = email.message_from_bytes(f.read())
    res = Email.get_eml_attachments(msg.as_bytes())
    assert res[0]["filename"] == "Test with an image.eml"

    # Test an email with EML attachment with an EML attachment
    with open("test_data/eml_test_with_attachment_with_eml_attachment.eml", "rb") as f:
        msg = email.message_from_bytes(f.read())
    res = Email.get_eml_attachments(msg.as_bytes())

    assert res[0]["filename"] == "Fwd: MOIS DE MARSè.eml"
    assert isinstance(res[0]["payload"], bytes)


@pytest.mark.parametrize(
    "cert_and_key",
    [
        # - cert and key are in the integration instance parameters
        # - private key is OpenSSL format
        # *** The cert and key below are not used in the real services, and only used for testing.
        (
            {
                "password": "-----BEGIN CERTIFICATE----- "
                "MIIDlzCCAX+gAwIBAgIUbN3atZY05K7SilRtY78y2ZON28QwDQYJKoZIhvcNAQEN "
                "BQAwJTEjMCEGA1UEAwwaTWFpbCBMaXN0ZW5lciBUZXN0IFJvb3QgQ0EwHhcNMjMw "
                "NzExMDA1NzI4WhcNMzMwNzExMDA1NzI4WjAaMRgwFgYDVQQDDA90ZXN0IGNsaWVu "
                "dCBlY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARFTRK4qjfOkK25NAssTni1/bKD "
                "TvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzERE "
                "gtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGujeDB2MB8GA1UdIwQYMBaAFDh6 "
                "N1cbIXsS4uo15Ha9fKZrEbcHMB0GA1UdDgQWBBRFSQMsVOPCmzMbvjnrYMGF1ZNs "
                "8DAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DAWBgNVHSUBAf8EDDAKBggr "
                "BgEFBQcDAjANBgkqhkiG9w0BAQ0FAAOCAgEAJKmlIV9Du9pnA98vw4GsurAeXU3Y "
                "KlzMyffzIVF+CGTpUFmXIGu1KccZREGQEZpxotYF71HCCqPBUcQD8rRoetxX3wa6 "
                "iqk6Q3Pm9Jt8/P365vydvcsKvTEeP8NTWKVip7U8xgAIjykBdnEPu9Uq7x+bePiG "
                "Pqd2Mpzr+mydbU/3mzrZXm/3B0aNiYZdXSpkF4qwZ7lakFvn0MI1M9+B2Am+rNdJ "
                "AoBBQTwS+1pUZoKV3gXMRWKCHj5cbltf1+Lzhh64A8s8k0o1cFyXfSFZ/PJI2rve "
                "ZOKGQ8qIeF3FPCaX5TVvla9J5Mxz5ETXv5zWpK/H4VgPbLf1cZPGFHnYatKkXMvM "
                "05UZ1FmdmJSS8CQQ7AwRsAyWOrbnfUf3Xv5UVFlgGYbsM1+ENbs9Mpn9mq0zq7+J "
                "ONxkmyrkP3Gi/ZK1k9fZuE+WGrnzkP6zUMA76Zr2uH8Gq5Bt89jTl9gAAYuaIDSe "
                "TQDQuO+Pb6XYiJaUg3LbkAnUSQHawZ6DfAMghevCPTIrTFLTUi8gILIpN2ghfv+z "
                "R2DE2xaKvNzNgEfPxR94haUGZy6eExteWFVbJAbQotux2poksrFqdgTW/7qntrpN "
                "l2AxOYvV/yu0yDjf/kyzt2aoWsbxClNv3jrbAj3m6raY/e6lcr6IuMYWMtO2F3n+ "
                "OzZEXmZyHr121wY= "
                "-----END CERTIFICATE----- "
                "-----BEGIN EC PRIVATE KEY----- "
                "MIGkAgEBBDCUBWVfn8bslTSkoWyA47lB8CwM/R5dlHrH4R52FkCmFFttnlotCt2v "
                "OCzaIX4lCIygBwYFK4EEACKhZANiAARFTRK4qjfOkK25NAssTni1/bKDTvEtmBFy "
                "5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzEREgtGiLBVZ "
                "f8YrYOBPHc93tFiWs7+z1C63uNRUVGs= "
                "-----END EC PRIVATE KEY-----"
            }
        ),
        # - cert and key are in the Certificate section of the Credentials
        # - private key is OpenSSL format
        # *** The cert and key below are not used in the real services, and only used for testing.
        (
            {
                "credentials": {
                    "sshkey": """
-----BEGIN CERTIFICATE-----
MIIDlzCCAX+gAwIBAgIUbN3atZY05K7SilRtY78y2ZON28QwDQYJKoZIhvcNAQEN
BQAwJTEjMCEGA1UEAwwaTWFpbCBMaXN0ZW5lciBUZXN0IFJvb3QgQ0EwHhcNMjMw
NzExMDA1NzI4WhcNMzMwNzExMDA1NzI4WjAaMRgwFgYDVQQDDA90ZXN0IGNsaWVu
dCBlY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARFTRK4qjfOkK25NAssTni1/bKD
TvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzERE
gtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGujeDB2MB8GA1UdIwQYMBaAFDh6
N1cbIXsS4uo15Ha9fKZrEbcHMB0GA1UdDgQWBBRFSQMsVOPCmzMbvjnrYMGF1ZNs
8DAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DAWBgNVHSUBAf8EDDAKBggr
BgEFBQcDAjANBgkqhkiG9w0BAQ0FAAOCAgEAJKmlIV9Du9pnA98vw4GsurAeXU3Y
KlzMyffzIVF+CGTpUFmXIGu1KccZREGQEZpxotYF71HCCqPBUcQD8rRoetxX3wa6
iqk6Q3Pm9Jt8/P365vydvcsKvTEeP8NTWKVip7U8xgAIjykBdnEPu9Uq7x+bePiG
Pqd2Mpzr+mydbU/3mzrZXm/3B0aNiYZdXSpkF4qwZ7lakFvn0MI1M9+B2Am+rNdJ
AoBBQTwS+1pUZoKV3gXMRWKCHj5cbltf1+Lzhh64A8s8k0o1cFyXfSFZ/PJI2rve
ZOKGQ8qIeF3FPCaX5TVvla9J5Mxz5ETXv5zWpK/H4VgPbLf1cZPGFHnYatKkXMvM
05UZ1FmdmJSS8CQQ7AwRsAyWOrbnfUf3Xv5UVFlgGYbsM1+ENbs9Mpn9mq0zq7+J
ONxkmyrkP3Gi/ZK1k9fZuE+WGrnzkP6zUMA76Zr2uH8Gq5Bt89jTl9gAAYuaIDSe
TQDQuO+Pb6XYiJaUg3LbkAnUSQHawZ6DfAMghevCPTIrTFLTUi8gILIpN2ghfv+z
R2DE2xaKvNzNgEfPxR94haUGZy6eExteWFVbJAbQotux2poksrFqdgTW/7qntrpN
l2AxOYvV/yu0yDjf/kyzt2aoWsbxClNv3jrbAj3m6raY/e6lcr6IuMYWMtO2F3n+
OzZEXmZyHr121wY=
-----END CERTIFICATE-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCUBWVfn8bslTSkoWyA47lB8CwM/R5dlHrH4R52FkCmFFttnlotCt2v
OCzaIX4lCIygBwYFK4EEACKhZANiAARFTRK4qjfOkK25NAssTni1/bKDTvEtmBFy
5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzEREgtGiLBVZ
f8YrYOBPHc93tFiWs7+z1C63uNRUVGs=
-----END EC PRIVATE KEY-----
"""
                }
            }
        ),
        # - cert and key are in the integration instance parameters
        # - private key is PKCS#8 PEM
        # *** The cert and key below are not used in the real services, and only used for testing.
        (
            {
                "password": "-----BEGIN CERTIFICATE----- "
                "MIIDlzCCAX+gAwIBAgIUbN3atZY05K7SilRtY78y2ZON28QwDQYJKoZIhvcNAQEN "
                "BQAwJTEjMCEGA1UEAwwaTWFpbCBMaXN0ZW5lciBUZXN0IFJvb3QgQ0EwHhcNMjMw "
                "NzExMDA1NzI4WhcNMzMwNzExMDA1NzI4WjAaMRgwFgYDVQQDDA90ZXN0IGNsaWVu "
                "dCBlY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARFTRK4qjfOkK25NAssTni1/bKD "
                "TvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzERE "
                "gtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGujeDB2MB8GA1UdIwQYMBaAFDh6 "
                "N1cbIXsS4uo15Ha9fKZrEbcHMB0GA1UdDgQWBBRFSQMsVOPCmzMbvjnrYMGF1ZNs "
                "8DAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DAWBgNVHSUBAf8EDDAKBggr "
                "BgEFBQcDAjANBgkqhkiG9w0BAQ0FAAOCAgEAJKmlIV9Du9pnA98vw4GsurAeXU3Y "
                "KlzMyffzIVF+CGTpUFmXIGu1KccZREGQEZpxotYF71HCCqPBUcQD8rRoetxX3wa6 "
                "iqk6Q3Pm9Jt8/P365vydvcsKvTEeP8NTWKVip7U8xgAIjykBdnEPu9Uq7x+bePiG "
                "Pqd2Mpzr+mydbU/3mzrZXm/3B0aNiYZdXSpkF4qwZ7lakFvn0MI1M9+B2Am+rNdJ "
                "AoBBQTwS+1pUZoKV3gXMRWKCHj5cbltf1+Lzhh64A8s8k0o1cFyXfSFZ/PJI2rve "
                "ZOKGQ8qIeF3FPCaX5TVvla9J5Mxz5ETXv5zWpK/H4VgPbLf1cZPGFHnYatKkXMvM "
                "05UZ1FmdmJSS8CQQ7AwRsAyWOrbnfUf3Xv5UVFlgGYbsM1+ENbs9Mpn9mq0zq7+J "
                "ONxkmyrkP3Gi/ZK1k9fZuE+WGrnzkP6zUMA76Zr2uH8Gq5Bt89jTl9gAAYuaIDSe "
                "TQDQuO+Pb6XYiJaUg3LbkAnUSQHawZ6DfAMghevCPTIrTFLTUi8gILIpN2ghfv+z "
                "R2DE2xaKvNzNgEfPxR94haUGZy6eExteWFVbJAbQotux2poksrFqdgTW/7qntrpN "
                "l2AxOYvV/yu0yDjf/kyzt2aoWsbxClNv3jrbAj3m6raY/e6lcr6IuMYWMtO2F3n+ "
                "OzZEXmZyHr121wY= "
                "-----END CERTIFICATE----- "
                "-----BEGIN PRIVATE KEY----- "
                "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCUBWVfn8bslTSkoWyA "
                "47lB8CwM/R5dlHrH4R52FkCmFFttnlotCt2vOCzaIX4lCIyhZANiAARFTRK4qjfO "
                "kK25NAssTni1/bKDTvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHx "
                "dKJkOY6p/VFpzEREgtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGs= "
                "-----END PRIVATE KEY-----"
            }
        ),
        # - cert and key are in the Certificate secion of the Credentials
        # - private key is PKCS#8 PEM
        # *** The cert and key below are not used in the real services, and only used for testing.
        (
            {
                "credentials": {
                    "sshkey": """
-----BEGIN CERTIFICATE-----
MIIDlzCCAX+gAwIBAgIUbN3atZY05K7SilRtY78y2ZON28QwDQYJKoZIhvcNAQEN
BQAwJTEjMCEGA1UEAwwaTWFpbCBMaXN0ZW5lciBUZXN0IFJvb3QgQ0EwHhcNMjMw
NzExMDA1NzI4WhcNMzMwNzExMDA1NzI4WjAaMRgwFgYDVQQDDA90ZXN0IGNsaWVu
dCBlY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARFTRK4qjfOkK25NAssTni1/bKD
TvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHxdKJkOY6p/VFpzERE
gtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGujeDB2MB8GA1UdIwQYMBaAFDh6
N1cbIXsS4uo15Ha9fKZrEbcHMB0GA1UdDgQWBBRFSQMsVOPCmzMbvjnrYMGF1ZNs
8DAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DAWBgNVHSUBAf8EDDAKBggr
BgEFBQcDAjANBgkqhkiG9w0BAQ0FAAOCAgEAJKmlIV9Du9pnA98vw4GsurAeXU3Y
KlzMyffzIVF+CGTpUFmXIGu1KccZREGQEZpxotYF71HCCqPBUcQD8rRoetxX3wa6
iqk6Q3Pm9Jt8/P365vydvcsKvTEeP8NTWKVip7U8xgAIjykBdnEPu9Uq7x+bePiG
Pqd2Mpzr+mydbU/3mzrZXm/3B0aNiYZdXSpkF4qwZ7lakFvn0MI1M9+B2Am+rNdJ
AoBBQTwS+1pUZoKV3gXMRWKCHj5cbltf1+Lzhh64A8s8k0o1cFyXfSFZ/PJI2rve
ZOKGQ8qIeF3FPCaX5TVvla9J5Mxz5ETXv5zWpK/H4VgPbLf1cZPGFHnYatKkXMvM
05UZ1FmdmJSS8CQQ7AwRsAyWOrbnfUf3Xv5UVFlgGYbsM1+ENbs9Mpn9mq0zq7+J
ONxkmyrkP3Gi/ZK1k9fZuE+WGrnzkP6zUMA76Zr2uH8Gq5Bt89jTl9gAAYuaIDSe
TQDQuO+Pb6XYiJaUg3LbkAnUSQHawZ6DfAMghevCPTIrTFLTUi8gILIpN2ghfv+z
R2DE2xaKvNzNgEfPxR94haUGZy6eExteWFVbJAbQotux2poksrFqdgTW/7qntrpN
l2AxOYvV/yu0yDjf/kyzt2aoWsbxClNv3jrbAj3m6raY/e6lcr6IuMYWMtO2F3n+
OzZEXmZyHr121wY=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCUBWVfn8bslTSkoWyA
47lB8CwM/R5dlHrH4R52FkCmFFttnlotCt2vOCzaIX4lCIyhZANiAARFTRK4qjfO
kK25NAssTni1/bKDTvEtmBFy5N0Qi+kisnUS05e9Okp2d8txhClwbjFbiunaNcHx
dKJkOY6p/VFpzEREgtGiLBVZf8YrYOBPHc93tFiWs7+z1C63uNRUVGs=
-----END PRIVATE KEY-----
"""
                }
            }
        ),
    ],
)
def test_load_client_cert_and_key(mocker, cert_and_key):
    """
    Given:
        Client cetifcates and private keys from the integration's parameters
    When:
        Authenticating the client using SSL client certificate authentication
    Then:
        1. Validate that the SSLContext object, that is used for authentication,
        is given the correct file that holds the certificates
        2. The function 'load_client_cert_and_key' returns True, inidicating that we are using
        SSL client certificate authentication
    """
    from MailListenerV2 import load_client_cert_and_key
    import ssl
    import tempfile

    params = {"clientCertAndKey": cert_and_key}

    named_temporary_file_mocker = mocker.patch(
        "MailListenerV2.NamedTemporaryFile", return_value=tempfile.NamedTemporaryFile(mode="w")
    )
    ssl_ctx = ssl.create_default_context()
    ssl_ctx.check_hostname = False
    ssl_ctx.verify_mode = ssl.CERT_NONE
    ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1_2
    load_cert_chain_mocker = mocker.patch.object(ssl_ctx, "load_cert_chain")
    assert load_client_cert_and_key(ssl_ctx, params) is True
    assert load_cert_chain_mocker.call_args_list[0][1].get("certfile") == named_temporary_file_mocker.return_value.name


def test_load_empty_client_cert_and_key_():
    """
    Given:
        Client cetifcates and private keys are not configured in the integration's parameters
    When:
        Authenticating the client
    Then:
        The function 'load_client_cert_and_key' returns False, inidicating that we are not
        using SSL client certificate authentication
    """
    from MailListenerV2 import load_client_cert_and_key
    import ssl

    # - No certificates and private keys
    params: dict[str, dict] = {"clientCertAndKey": {}}

    ssl_ctx = ssl.create_default_context()
    ssl_ctx.check_hostname = False
    ssl_ctx.verify_mode = ssl.CERT_NONE
    ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1_2
    assert load_client_cert_and_key(ssl_ctx, params) is False


@pytest.mark.parametrize(
    "input_credentials, output_credentials",
    [
        # No credentials
        (None, None),
        # 1 credential
        (
            "-----BEGIN CERTIFICATE----- LINE1 LINE2 -----END CERTIFICATE-----",
            """-----BEGIN CERTIFICATE-----
LINE1
LINE2
-----END CERTIFICATE-----""",
        ),
        # 2 credentials
        (
            "-----BEGIN CERTIFICATE----- "
            "LINE1 "
            "LINE2 "
            "-----END CERTIFICATE----- "
            "-----BEGIN EC PRIVATE KEY----- "
            "LINE1 "
            "LINE2 "
            "-----END EC PRIVATE KEY-----",
            """-----BEGIN CERTIFICATE-----
LINE1
LINE2
-----END CERTIFICATE-----
-----BEGIN EC PRIVATE KEY-----
LINE1
LINE2
-----END EC PRIVATE KEY-----""",
        ),
        # credentials with human readable text
        (
            "text1 text2 -----BEGIN EC PRIVATE KEY----- LINE1 LINE2 -----END EC PRIVATE KEY----- text1 text2",
            """text1 text2
-----BEGIN EC PRIVATE KEY-----
LINE1
LINE2
-----END EC PRIVATE KEY-----
text1 text2""",
        ),
    ],
)
def test_replace_spaces_in_credentials(input_credentials, output_credentials):
    """
    Given:
        Client cetifcates and private keys
    When:
        Authenticating the client
    Then:
        Check that the spaces in the credentials are replaced with new lines if they are in the correct format.
    """
    from MailListenerV2 import replace_spaces_in_credentials
    import json

    assert json.dumps(replace_spaces_in_credentials(input_credentials)) == json.dumps(output_credentials)


def test_fetch_incidents__last_uid_as_int(mocker):
    """
    Given:
        - A mock client and last run with 'last_uid' as an integer - 8
    When:
        - Fetching incidents
    Then:
        - Ensure that the "last_uid" received from the 'last_run' of previous cycles is converted to an integer.
        Also, verify that the 'last_uid' to be written in the 'last_run' for the next cycle is a string.
    """
    from MailListenerV2 import fetch_incidents

    mocker.patch("MailListenerV2.Email.convert_to_incident", return_value={})
    fetch_mail_mocker = mocker.patch("MailListenerV2.fetch_mails", return_value=([mock_email()], [mock_email()], 5))

    next_run, _ = fetch_incidents(
        client=mocker.Mock(),
        last_run={"last_uid": 8},
        first_fetch_time="2022-01-01 00:00:00",
        include_raw_body=False,
        with_headers=False,
        permitted_from_addresses="test@example.com",
        permitted_from_domains="example.com",
        delete_processed=False,
        limit=10,
        save_file=False,
        date_fetch=False,
    )
    assert isinstance(fetch_mail_mocker.call_args[1]["uid_to_fetch_from"], int)
    assert isinstance(next_run["last_uid"], str)


def test_fetch_incidents__last_uid_as_string(mocker):
    """
    Given:
        - A mock client and last run with 'last_uid' as a string - "8"
    When:
        - Fetching incidents
    Then:
        - Ensure that the "last_uid" received from the 'last_run' of previous cycles is converted to an integer.
        Also, verify that the 'last_uid' to be written in the 'last_run' for the next cycle is a string.
    """
    from MailListenerV2 import fetch_incidents

    mocker.patch("MailListenerV2.Email.convert_to_incident", return_value={})
    fetch_mail_mocker = mocker.patch("MailListenerV2.fetch_mails", return_value=([mock_email()], [mock_email()], 5))

    next_run, _ = fetch_incidents(
        client=mocker.Mock(),
        last_run={"last_uid": "8"},
        first_fetch_time="2022-01-01 00:00:00",
        include_raw_body=False,
        with_headers=False,
        permitted_from_addresses="test@example.com",
        permitted_from_domains="example.com",
        delete_processed=False,
        limit=10,
        save_file=False,
        date_fetch=False,
    )
    assert isinstance(fetch_mail_mocker.call_args[1]["uid_to_fetch_from"], int)
    assert isinstance(next_run["last_uid"], str)


def test_fetch_incidents__last_uid_was_zero(mocker):
    """
    Given:
        - A mock client and last run with 'last_uid' as a string - "0"
    When:
        - Fetching incidents
    Then:
        - Ensure that the next run is None, since setting it to "0" will cause an error in the next cycle.

    """
    from MailListenerV2 import fetch_incidents

    mocker.patch("MailListenerV2.Email.convert_to_incident", return_value={})
    mocker.patch("MailListenerV2.fetch_mails", return_value=([mock_email()], [mock_email()], 0))

    next_run, _ = fetch_incidents(
        client=mocker.Mock(),
        last_run={"last_uid": "0"},
        first_fetch_time="2022-01-01 00:00:00",
        include_raw_body=False,
        with_headers=False,
        permitted_from_addresses="test@example.com",
        permitted_from_domains="example.com",
        delete_processed=False,
        limit=10,
        save_file=False,
        date_fetch=False,
    )
    assert next_run == {"last_date": "2022-01-01T00:00:00+00:00"}


def test_fetch_incidents__email_without_date(mocker):
    """
    Given:
        - A fetched email whose Date header could not be parsed (Email.date is None),
          together with a dated email, while fetching by date (date_fetch=True) with an existing last_date.
    When:
        - Fetching incidents.
    Then:
        - Ensure the fetch does not raise a TypeError from max(datetime, None),
          the undated email is still returned as an incident, and the date cursor
          advances to the newest available (dated) email.
    """
    from MailListenerV2 import fetch_incidents

    mocker.patch("MailListenerV2.Email.convert_to_incident", return_value={})
    dated_email = mock_email()
    dated_email.id = 6
    undated_email = mock_email_without_date(mail_id=7)
    mocker.patch("MailListenerV2.fetch_mails", return_value=([dated_email, undated_email], [dated_email, undated_email], 7))

    next_run, incidents = fetch_incidents(
        client=mocker.Mock(),
        last_run={"last_uid": "5", "last_date": "2020-01-01T00:00:00+00:00"},
        first_fetch_time="2022-01-01 00:00:00",
        include_raw_body=False,
        with_headers=False,
        permitted_from_addresses="test@example.com",
        permitted_from_domains="example.com",
        delete_processed=False,
        limit=10,
        save_file=False,
        date_fetch=True,
    )
    # Both emails are ingested (undated one is not dropped).
    assert len(incidents) == 2
    # Cursor advanced to the dated email's date, ignoring the None date.
    assert next_run["last_date"] == "2020-10-01T00:00:00+00:00"
    assert next_run["last_uid"] == "7"


def test_fetch_incidents__only_email_without_date(mocker):
    """
    Given:
        - The only fetched email has no parseable Date header (Email.date is None),
          with date_fetch disabled and no prior last_date. In this first-fetch path
          time_to_fetch_from is initialized from first_fetch_time, while mail.date is None.
    When:
        - Fetching incidents.
    Then:
        - Ensure the fetch does not raise (the None mail.date is ignored in the max()),
          the undated email is still ingested, and the date cursor keeps the
          first_fetch_time value while the UID cursor advances.
    """
    import demistomock as demisto
    from MailListenerV2 import fetch_incidents

    mocker.patch("MailListenerV2.Email.convert_to_incident", return_value={})
    debug_mocker = mocker.patch.object(demisto, "debug")
    undated_email = mock_email_without_date(mail_id=7)
    mocker.patch("MailListenerV2.fetch_mails", return_value=([undated_email], [undated_email], 7))

    next_run, incidents = fetch_incidents(
        client=mocker.Mock(),
        last_run={"last_uid": "5"},
        first_fetch_time="2022-01-01 00:00:00",
        include_raw_body=False,
        with_headers=False,
        permitted_from_addresses="test@example.com",
        permitted_from_domains="example.com",
        delete_processed=False,
        limit=10,
        save_file=False,
        date_fetch=False,
    )
    assert len(incidents) == 1
    assert next_run["last_date"] == "2022-01-01T00:00:00+00:00"
    assert next_run["last_uid"] == "7"
    # Ensure we logged evidence that the email had no parseable Date header.
    assert any("has no parseable Date header" in str(call.args[0]) for call in debug_mocker.call_args_list if call.args)


def test_fetch_mails__mail_id_is_greater(mocker):
    """
    Given:
        - A mock client and last run with uid_to_fetch_from == 2
        - The email UID returend from the client are  [1, 2, 3]
    When:
        - Fetching incidents
    Then:
        - Ensure that next_uid_to_fetch_from is 3 since it is greater than the last run
    """
    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mocker.patch("MailListenerV2.Email")
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(IMAPClient, "search", return_value=[1, 2, 3])
    mocker.patch.object(IMAPClient, "fetch")
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    _, _, next_uid_to_fetch_from = fetch_mails(IMAPClient("http://example_url.com"), uid_to_fetch_from=2)
    assert next_uid_to_fetch_from == 3


def test_fetch_mails__last_run_is_greater(mocker):
    """
    Given:
        - A mock client and last run with uid_to_fetch_from == 4
        - The email UID returend from the client are  [1, 2, 3]
    When:
        - Fetching incidents
    Then:
        - Ensure that the next_uid_to_fetch_from is 4 since it is greater than the greatest email UID
    """
    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mocker.patch("MailListenerV2.Email")
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(IMAPClient, "search", return_value=[1, 2, 3])
    mocker.patch.object(IMAPClient, "fetch")
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    _, _, next_uid_to_fetch_from = fetch_mails(IMAPClient("http://example_url.com"), uid_to_fetch_from=4)
    assert next_uid_to_fetch_from == 4


def test_fetch_mails__uid_is_str(mocker):
    """
    Given:
        - The email UIDs returend from the client are strings ['1', '2', '3']
    When:
        - Fetching incidents
    Then:
        - Ensure that the next_uid_to_fetch_from is 4 since it is greater than the greatest email UID
    """
    from MailListenerV2 import fetch_mails
    import demistomock as demisto
    from imapclient import IMAPClient

    mocker.patch("MailListenerV2.Email")
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(IMAPClient, "search", return_value=["1", "2", "3"])
    mocker.patch.object(IMAPClient, "fetch")
    mocker.patch.object(IMAPClient, "_create_IMAP4")
    _, _, next_uid_to_fetch_from = fetch_mails(IMAPClient("http://example_url.com"), uid_to_fetch_from="4")
    assert next_uid_to_fetch_from == 4