MongoDB

Use the MongoDB integration to search and query entries in your MongoDB.

Database · MongoDB

Details

IDMongoDB
ProviderMongoDB Inc.
CategoryDatabase
From Version5.0.0
Docker Imagedemisto/py3-tools:1.0.0.3040909
Supported ModulesAgentix XSIAM

README

Overview


Use MongoDB to search and query entries
This integration was integrated and tested with version v4.2.3 of MongoDB

Configure MongoDB on Cortex XSOAR


  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for MongoDB.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Username
    • Server URLs with port (host1.com:27017,host2.com:27017)
    • Database
    • Trust any certificate (not secure)
  4. Click Test to validate the URLs, token, and connection.

Fetched Incidents Data


Commands


You can execute these commands from the Cortex XSOAR 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.

  1. mongodb-get-entry-by-id
  2. mongodb-query
  3. mongodb-insert
  4. mongodb-update
  5. mongodb-delete
  6. mongodb-list-collections
  7. mongodb-create-collection
  8. mongodb-drop-collection
  9. mongodb-pipeline-query

1. mongodb-get-entry-by-id


Get an entry from database by ID

Required Permissions

find permission.

Base Command

mongodb-get-entry-by-id

Input
Argument Name Description Required
collection Name of the collection do get entry from. Required
object_id An ObjectID to get. Required
Context Output
Path Type Description
MongoDB.Entry._id String ID of entry
MongoDB.Entry.collection String Collection name
Command Example

!mongodb-get-entry-by-id collection=test object_id=5e444002d661d4fc62442f39

Context Example
{
    "MongoDB": [
        {
            "test": true, 
            "_id": "5e444002d661d4fc62442f39"
        } 
    ]
}
Human Readable Output

Total of 0 found in MongoDB collection ‘test’:
No entries.

2. mongodb-query


Searches for items by using the specified JSON query. Search by regex is supported.

Required Permissions

find permission.

Base Command

mongodb-query

Input
Argument Name Description Required
collection Name of the collection do query from. Required
query A JSON query to search for in the collection, in the format of: {"key": "value"}. e.g {“_id”: “mongodbid”}. Supports search by regex using the following query="{ "field": { "$regex": "search_option" } }". For example: query="{ "year": { "$regex": "2.*" } }" - will query all entries such that their “year” field contains the number 2, query="{ "color": { "$regex": "Re.*", "$options": "i" } }": case insensitive search - will query all entries at the collection, where their “color” field contains the string “Re”. Required
sort Sorting order for the query results. Use the format “field1:asc,field2:desc”. Optional
Context Output
Path Type Description
MongoDB.Entry._id String ID of entry from query
MongoDB.Entry.collection String Collection name
Command Example

!mongodb-query collection=test query=`{"test": true}

Context Example
{
    "MongoDB": [
        {
            "test": true, 
            "_id": "5e454023a14c0fb64ca2fd7f"
        }, 
        {
            "test": true, 
            "_id": "5e454024a14c0fb64ca2fd80"
        }, 
        {
            "test": true, 
            "_id": "5e454024a14c0fb64ca2fd81"
        }
    ]
}
Human Readable Output

Total of 2 found in MongoDB collection ‘test’ with query: {“test”: true}:

_id
5e454023a14c0fb64ca2fd7f
5e454024a14c0fb64ca2fd80

3. mongodb-insert


Inserts an entry to the database

Required Permissions

insert permission.

Base Command

mongodb-insert

Input
Argument Name Description Required
collection Name of the collection to insert entry from. Required
entry Entry JSON formatted. can include _id argument or not. Required
Context Output
Path Type Description
MongoDB.Entry._id String ID of entry from query.
MongoDB.Entry.collection String Collection name
Command Example

!mongodb-insert collection=testCollection entry=`{"test": true}`\

Context Example
{
    "MongoDB": [
        {
            "_id": "5e45403c7bc040c2a989007a"
        }
    ]
}
Human Readable Output

MongoDB: Successfully entered 1 entry to the ‘testCollection’ collection.

_id
5e45403c7bc040c2a989007a

4. mongodb-update


Updates an entry in a collection

Required Permissions

update permission.

Base Command

mongodb-update

Input
Argument Name Description Required
collection Name of the collection to update entry to. Required
filter A query that matches the document to update. Required
update You can use Update Operators or Aggregation Pipeline. Check documentation for further information. Required
update_one Update only one entry. if true, will set all found entries. Optional
upsert Update entries in a collection that matches the query or create a new entry if no entires match the query. Default is false. Optional
Context Output

There is no context output for this command.

Command Example

!mongodb-update collection=test filter=`{"test": true}` update=`{"$set": {"test": false}}`

Human Readable Output

MongoDB: Total of 1 entries has been modified.

If an entry was created and inserted using the upset argument:

MongoDB: A new entry was inserted to the collection.

5. mongodb-delete


Deletes an entry from the database

Required Permissions

remove permission.

Base Command

mongodb-delete

Input
Argument Name Description Required
collection Name of the collection to delete entry from. Required
filter A query that matches the document to delete. Required
delete_one Delete only one entry from the database. Optional
Context Output

There is no context output for this command.

Command Example

!mongodb-delete collection=test filter=`{"test": true}` delete_one=true

Human Readable Output

MongoDB: Delete 1 entries.

6. mongodb-list-collections


Lists all collections in database

Required Permissions

find permission.

Base Command

mongodb-list-collections

Input

There are no input arguments for this command.

Context Output
Path Type Description
MongoDB.Collection.Name String Name of the collection
Command Example

##### Context Example

```json
{
    "MongoDB.Collection": [
        {
            "Name": "collectionToDelete"
        }, 
        {
            "Name": "testCollection"
        }, 
        {
            "Name": "test"
        }
    ]
}
Human Readable Output

MongoDB: All collections in database:

Collection
collectionToDelete
testCollection
test

7. mongodb-create-collection


Creates a collection

Required Permissions

createCollection permission.

Base Command

mongodb-create-collection

Input
Argument Name Description Required
collection Name of collection to create. Required
Context Output

There is no context output for this command.

Command Example

!mongodb-create-collection collection=testCollection

Human Readable Output

MongoDB: Collection ‘testCollection’ has been successfully created.

8. mongodb-drop-collection


Drops a collection from the database

Required Permissions

dropCollection permission or above.

Base Command

mongodb-drop-collection

Input
Argument Name Description Required
collection Name of collection to be dropped Required
Context Output

There is no context output for this command.

Command Example

!mongodb-drop-collection collection=collectionToDelete

Human Readable Output

MongoDB: Collection ‘collectionToDelete` has been dropped.

9. mongodb-pipeline-query


Searches for items by the specified JSON pipleline query.

Base Command

mongodb-pipeline-query

Input

Argument Name Description Required
collection Name of the collection to query. Required
pipeline A JSON pipeline query to search by in the collection. Pipeline query should by list of dictionaries. For example: [{“key1”: “value1”}, {“key2”: “value2”}]. Required
limit Limits the number of results returned from MongoDB. Default is 50. Optional
offset Offset to the first result returned from MongoDB. Default is 0. Optional

Context Output

Path Type Description
MongoDB.Entry._id String The ID of entry from the query.
MongoDB.Entry.collection String The collection of which the entry belongs to.

Command Example

!mongodb-pipeline-query collection=test_collection pipeline="[{\"$match\": {\"title\": \"test_title\"}}]"

Context Example

{
    "MongoDB": {
        "Entry": [
            {
                "_id": "602e624e8be6cb93eb795695",
                "collection": "test_collection",
                "color": "red",
                "title": "test_title",
                "year": "2019"
            },
            {
                "_id": "602e62598be6cb93eb795697",
                "collection": "test_collection",
                "color": "green",
                "title": "test_title",
                "year": "2020"
            },
            {
                "_id": "602e62698be6cb93eb795699",
                "collection": "test_collection",
                "color": "yellow",
                "title": "test_title",
                "year": "2018"
            }
        ]
    }
}

Human Readable Output

Total of 3 entries were found in MongoDB collection test_collection with pipeline: [{“$match”: {“title”: “test_title”}}]:

_id
602e624e8be6cb93eb795695
602e62598be6cb93eb795697
602e62698be6cb93eb795699

10. mongodb-bulk-update


Bulk updates entries in a collection.

Required Permissions

update permission.

Base Command

mongodb-bulk-update

Input
Argument Name Description Required
collection The name of the collection in which to update entries. Required
filter A comma-separated list of queries that match the documents to update, in the format: `[{"key1": "value1"},{"key2": "value2"}]`. This list must match the comma-separated list of the update argument by order and size. Required
update A comma-separated list of content with which to update entries, in the format: `[{"$set": {"key1": "value1"}},{"$set": {"key2": "value2"}}]`. You can use Update Operators or Aggregation Pipeline. This list must match the comma-separated list of the filter argument by order and size. Required
update_one Whether to update a single entry per query. If true, will set only the first found entry, If false, will set all found entries. This argument will effect all the provided queries. Default is true. Optional
upsert Will create a new entry if no entires match the provided queries (per query). This argument will effect all the provided queries. Default is false. Optional
Context Output

There is no context output for this command.

Command Example

!mongodb-update collection=test filter=`[{"Name": "dummy1"},{"$and": [{"value": 1, "another_value":0}]}]` update=`[{"$set": {"test": false}},{"$set": {"test": true}}]` upsert=true

Human Readable Output

MongoDB: Total of 1 entries has been modified.
MongoDB: Total of 1 entries has been inserted.

Additional Information


  • a guide on how to use the filter and query argument can be found here
  • a guide on how to use the update argument can be found here

Known Limitations


The test button is trying to list collections. If the user has no find permission it will fail.

Configuration parameters

  • credentials — Username (required)
  • urls — Server URLs with port (host1.com:27017,host2.com:27017) (required)
  • database — Database Name (required)
  • auth_source — Auth Source
  • use_ssl — Use SSL/TLS secured connection
  • insecure — Trust any certificate (not secure)

Commands (10)

  • mongodb-bulk-update

    Bulk updates entries in a collection.

  • mongodb-create-collection

    Creates a collection.

  • mongodb-delete

    Deletes an entry from the database.

  • mongodb-drop-collection

    Drops a collection from the database.

  • mongodb-get-entry-by-id

    Gets an entry from the database by the object ID.

  • mongodb-insert

    Inserts an entry to the database.

  • mongodb-list-collections

    Lists all collections in the database.

  • mongodb-pipeline-query

    Searches for items by the specified JSON pipleline query.

  • mongodb-query

    Searches for items by using the specified JSON query. Search by regex is supported.

  • mongodb-update

    Updates an entry in a collection.

import copy
from datetime import datetime
from unittest.mock import patch, MagicMock
import pytest
from bson.objectid import ObjectId
from CommonServerPython import DemistoException
from MongoDB import (
    Client,
    bulk_update_command,
    convert_id_to_object_id,
    convert_object_id_to_str,
    convert_str_to_datetime,
    format_sort,
    parse_and_validate_bulk_update_arguments,
    pipeline_query_command,
    search_query,
    update_entry_command,
)

id_to_obj_inputs = [
    (
        [{"_id": "5e4412f230c5b8f63a7356ba"}],
        [{"_id": ObjectId("5e4412f230c5b8f63a7356ba")}],
    ),
    (
        {"_id": "5e4412f230c5b8f63a7356ba"},
        {"_id": ObjectId("5e4412f230c5b8f63a7356ba")},
    ),
    (
        {"_id": {"$gte": "5e4412f230c5b8f63a7356ba"}},
        {"_id": {"$gte": ObjectId("5e4412f230c5b8f63a7356ba")}},
    ),
    ({}, {}),
    ({"id": 1}, {"id": 1}),
]


@pytest.mark.parametrize("func_input, expected", id_to_obj_inputs)
def test_convert_id_to_object_id(func_input, expected):
    assert expected == convert_id_to_object_id(func_input)


object_to_id = [
    ([ObjectId("5e4412f230c5b8f63a7356ba")], ["5e4412f230c5b8f63a7356ba"]),
    (
        [ObjectId("5e4412f230c5b8f63a7356ba"), ObjectId("5e4412f230c5b8f63a7356ba")],
        ["5e4412f230c5b8f63a7356ba", "5e4412f230c5b8f63a7356ba"],
    ),
]


@pytest.mark.parametrize("func_input, expected", object_to_id)
def test_convert_object_id_to_str(func_input, expected):
    assert expected == convert_object_id_to_str(func_input)


def test_normalize_id():
    res = Client.normalize_id({"_id": ObjectId("5e4412f230c5b8f63a7356ba")})
    assert res["_id"] == "5e4412f230c5b8f63a7356ba"


class TestConvertStrToDatetime:
    dict_inputs = [
        {"testing": 123, "time": "ISODate('2020-06-12T08:23:07.000Z')"},
        pytest.param({"testing": 123, "time": "ISODate('2018-06-12T08:23:07.000')"}, marks=pytest.mark.xfail),
    ]

    @pytest.mark.parametrize("func_input", dict_inputs)
    def test_convert_str_to_datetime(self, func_input):
        res = convert_str_to_datetime(func_input)
        assert isinstance(res["time"], datetime)

    def test_convert_str_to_datetime_no_datetime_obj(self):
        inputs = {1: 2}
        res = convert_str_to_datetime(inputs)
        assert isinstance(res[1], int)

    def test_nested_dict(self):
        """
        Given:
        A nested dict with a timestamp

        When:
        Running a query or insert

        Then:
        Validating all keys in the dict are there and the timestamp is valid

        """
        func_input = {"k": {"$gte": "ISODate('2020-06-12T08:23:07.000Z')"}}
        res = convert_str_to_datetime(func_input)
        assert isinstance(res["k"]["$gte"], datetime)


class TestDatetimeToStr:
    datetime_obj = datetime.strptime("2020-05-19T09:05:28.000Z", "%Y-%m-%dT%H:%M:%S.000Z")
    datetime_str = "2020-05-19T09:05:28.000Z"

    def test_datetime_to_str_dict(self):
        """
        Given:
            dict containing datetime object

        When:
            converting datetimes to strs

        Then:
            validate the value is a string.
        """
        raw = Client.datetime_to_str({"time": self.datetime_obj})
        assert self.datetime_str == raw["time"]

    def test_datetime_to_str_list(self):
        """
        Given:
            list containing datetime object

        When:
            converting datetimes to strs

        Then:
            validate the value is a string.
        """
        raw = Client.datetime_to_str([self.datetime_obj])
        assert [self.datetime_str] == raw

    def test_datetime_to_str_str(self):
        """
        Given:
            datetime object

        When:
            converting datetimes to strs

        Then:
            validate the value is a string.
        """
        raw = Client.datetime_to_str(self.datetime_obj)
        assert self.datetime_str == raw

    def test_datetime_to_str_dict_no_datetime(self):
        """
        Given:
            dict containing 5 (int)

        When:
            converting datetimes to strs

        Then:
            validate the value returned is 5
        """
        raw = Client.datetime_to_str({"time": 5})
        assert raw["time"] == 5

    def test_datetime_to_str_list_no_datetime(self):
        """
        Given:
            list containing an int (5) object

        When:
            converting datetimes to strs

        Then:
            validate the value returned is 5.
        """
        raw = Client.datetime_to_str([5])
        assert raw == [5]

    def test_datetime_to_str_str_no_datetime(self):
        """
        Given:
            'str'

        When:
            converting datetimes to strs

        Then:
            validate the value returned is 'str'.
        """
        raw = Client.datetime_to_str("str")
        assert raw == "str"


class MockedQuery:
    class Limit:
        @staticmethod
        def limit(number):
            return [{"time": TestDatetimeToStr.datetime_obj, "_id": ObjectId("5e4412f230c5b8f63a7356ba")}]

    @classmethod
    def find(cls, query):
        return cls.Limit


def test_query(mocker):
    """
    Given:
        Object with datetime and object id in it

    When:
        Quering object

    Then:
        validate all objects returned are strs.
    """
    client = Client(["aaaaa"], "a", "b", "d")
    mocker.patch.object(Client, "get_collection", return_value=MockedQuery)
    readable_outputs, outputs, raw_response = search_query(client, "a", "{}", "50")
    time = raw_response[0]["time"]
    _id = raw_response[0]["_id"]
    assert isinstance(_id, str)
    assert isinstance(time, str)


class TestFormatSort:
    def test_format_sort_correctly(self):
        """
        Given:
            a sort string in the correct format
        Then:
            Format the string in the correct format to be used in `pymongo.sort()`
        """
        assert format_sort("field1:asc,field2:desc") == [("field1", 1), ("field2", -1)]
        assert format_sort("field1:asc") == [("field1", 1)]

    def test_format_sort_raises_error(self):
        """
            Given:
            a sort string in the wrong format
        Then:
            raise a ValueError
        """
        with pytest.raises(ValueError):
            format_sort("Wrong:Type")
        with pytest.raises(ValueError):
            format_sort("WrongType")


def test_pipeline_query_command(mocker):
    """
    Given:
        collection - where to search.
        pipeline - json pipeline query

    When:
        calling `pipeline_query_command`

    Then:
        validate the readable output and context
    """
    client = Client(["aaaaa"], "a", "b", "d")
    return_value = [
        {"title": "test_title", "color": "red", "year": "2019", "_id": "6034a5a62f605638740dba55"},
        {"title": "test_title", "color": "yellow", "year": "2020", "_id": "6034a5c52f605638740dba57"},
    ]
    mocker.patch.object(client, "pipeline_query", return_value=return_value)
    readable_outputs, outputs, raw_response = pipeline_query_command(
        client=client, collection="test_collection", pipeline='[{"$match": {"title": "test_title"}}]'
    )

    expected_context = []
    for item in copy.deepcopy(raw_response):
        item.update({"collection": "test_collection"})
        expected_context.append(item)

    assert "Total of 2 entries were found in MongoDB collection" in readable_outputs
    assert outputs.get("MongoDB.Entry(val._id === obj._id && obj.collection === val.collection)") == expected_context


class MockResponse:
    """Mock response for TestUpdateQueryCommands and TestBulkUpdateQueryCommands classes.
    represents a partial SDK response of the update_entry and bulk_update_entries functions.
    """

    def __init__(self, acknowledged, modified_count, upserted_count, upserted_id=False):
        self.acknowledged = acknowledged
        self.modified_count = modified_count
        self.upserted_count = upserted_count
        self.upserted_id = upserted_id


class TestUpdateQueryCommands:
    """Class for update_query_command UTs."""

    client = Client(["aaaaa"], "a", "b", "d")
    case_upsert_with_no_matching_entry = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        True,
        True,
        MockResponse(True, 0, 0, 1),
        "A new entry was inserted to the collection.",
    )
    case_upsert_with_one_matching_entry = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        True,
        True,
        MockResponse(True, 1, 0, 0),
        "MongoDB: Total of 1 entries has been modified.",
    )
    case_upsert_with_many_matching_entry = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        False,
        True,
        MockResponse(True, 5, 0, 0),
        "MongoDB: Total of 5 entries has been modified.",
    )
    case_upsert_with_matching_entry_no_modifications = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        True,
        True,
        MockResponse(True, 0, 0, 0),
        "MongoDB: Total of 0 entries has been modified.",
    )
    case_upsert_with_many_matching_entries_update_only_one = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        True,
        True,
        MockResponse(True, 1, 0, 0),
        "MongoDB: Total of 1 entries has been modified.",
    )
    case_no_upsert_with_no_matching_entry = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        True,
        False,
        MockResponse(True, 0, 0, 0),
        "MongoDB: Total of 0 entries has been modified.",
    )

    update_query_cases = [
        case_upsert_with_no_matching_entry,
        case_upsert_with_one_matching_entry,
        case_upsert_with_many_matching_entry,
        case_upsert_with_matching_entry_no_modifications,
        case_upsert_with_many_matching_entries_update_only_one,
        case_no_upsert_with_no_matching_entry,
    ]

    @pytest.mark.parametrize("filter, update, update_one, upsert, response, expected", update_query_cases)
    def test_update_entry_command(self, mocker, filter, update, update_one, upsert, response, expected, client=client):
        """
        Given:
            valid arguments

        When:
            running mongodb-update command in XSOAR

        Then:
            the expected human readable is returned
        """
        mocker.patch.object(client, "update_entry", return_value=response)
        return_value = update_entry_command(
            client, "test_collection", filter=filter, update=update, update_one=update_one, upsert=upsert
        )
        assert return_value[0] == expected

    case_invalid_filter_argument = (
        '"Name": "dummy"}',
        '{"$set":{"test":0}}',
        MockResponse(True, 0, 0, 0),
        'The `filter` argument is not a valid json. Valid input example: `{"key": "value"}`',
    )
    case_invalid_update_argument = (
        '{"Name": "dummy"}',
        '"$set":{"test":0}}',
        MockResponse(True, 0, 0, 0),
        'The `update` argument is not a valid json. Valid input example: `{"$set": {"key": "value"}`',
    )
    case_invalid_response = (
        '{"Name": "dummy"}',
        '{"$set":{"test":0}}',
        None,
        "Error occurred when trying to enter update entries.",
    )

    invalid_cases = [case_invalid_filter_argument, case_invalid_update_argument, case_invalid_response]

    @pytest.mark.parametrize("filter, update, response, expected", invalid_cases)
    def test_update_entry_command_fail(self, mocker, filter, update, response, expected, client=client):
        """
        Given:
            invalid arguments

        When:
            running mongodb-update command in XSOAR

        Then:
            the expected error message is raised
        """
        mocker.patch.object(client, "update_entry", return_value=response)
        try:
            update_entry_command(client, "test_collection", filter=filter, update=update)
        except DemistoException as e:
            assert str(e) == expected


class TestBulkUpdateQueryCommands:
    """Class for bulk_update_query_command UTs."""

    client = Client(["aaaaa"], "a", "b", "d")
    # valid command arguments
    case_single_update_args = ('[{"Name": "dummy"}]', '[{"$set":{"test":0}}]', ([{"Name": "dummy"}], [{"$set": {"test": 0}}]))
    case_simple_bulk_update_args = (
        '[{"Name": "dummy1"},{"Name": "dummy2"}]',
        '[{"$set":{"test":1}},{"$set":{"test":2}}]',
        ([{"Name": "dummy1"}, {"Name": "dummy2"}], [{"$set": {"test": 1}}, {"$set": {"test": 2}}]),
    )
    case_bulk_update_complex_filter = (
        '[{"$and": [{"value":0,"another_value":1}],"Name":"dummy1",\
            "less_than": {"$lt":3000}},{"Name":"dummy2"}]',
        '[{"$set":{"test":1}},{"$set":{"test":2}}]',
        (
            [{"$and": [{"value": 0, "another_value": 1}], "less_than": {"$lt": 3000}, "Name": "dummy1"}, {"Name": "dummy2"}],
            [{"$set": {"test": 1}}, {"$set": {"test": 2}}],
        ),
    )
    case_bulk_update_complex_update = (
        '[{"Name":"dummy1"},{"Name":"dummy2"}]',
        '[{"$set":{"test":1,"value":2,"another_value":{"sub_value": 4}}},{"$set":{"test":2}}]',
        (
            [{"Name": "dummy1"}, {"Name": "dummy2"}],
            [{"$set": {"test": 1, "value": 2, "another_value": {"sub_value": 4}}}, {"$set": {"test": 2}}],
        ),
    )
    case_bulk_update_context_args = (
        [{"Name": "dummy1"}, {"Name": "dummy2"}],
        [{"$set": {"test": 1}}, {"$set": {"test": 2}}],
        ([{"Name": "dummy1"}, {"Name": "dummy2"}], [{"$set": {"test": 1}}, {"$set": {"test": 2}}]),
    )

    # invalid command arguments
    case_missing_brackets = (
        '{"Name": "dummy1"},{"Name": "dummy2"}]',
        '[{"$set":{"test":1}},{"$set":{"test":2}}]',
        "The `filter` argument must be a json array.",
    )
    case_not_matching_number_of_filters_and_updates = (
        '[{"Name": "dummy1"},{"Name": "dummy2"}]',
        '[{"$set":{"test":1}}]',
        "The `filter` and `update` arguments must contain the same number of elements.",
    )
    case_invalid_json = (
        '{"Name": "dummy1"},{"Name": "dummy2"]',
        '[{"$set":{"test":1}},{"$set":{"test":2}}]',
        "The `filter` argument contains an invalid json.",
    )

    @pytest.mark.parametrize(
        "filter, update, expected_output",
        [
            case_single_update_args,
            case_simple_bulk_update_args,
            case_bulk_update_complex_filter,
            case_bulk_update_complex_update,
            case_bulk_update_context_args,
        ],
    )
    def test_parse_and_validate_bulk_update_arguments(self, filter, update, expected_output):
        """
        Given:
            valid arguments for bulk update command

        When:
            running mongodb-bulk-update command in XSOAR

        Then:
            parse_and_validate_bulk_update_arguments will parse validate the filter and update arguments
        """
        filter_list, update_list = parse_and_validate_bulk_update_arguments(filter, update)
        assert filter_list == expected_output[0]
        assert update_list == expected_output[1]

    @pytest.mark.parametrize(
        "filter, update, error_message",
        [case_missing_brackets, case_not_matching_number_of_filters_and_updates, case_invalid_json],
    )
    def test_parse_and_validate_bulk_update_arguments_fail(self, filter, update, error_message):
        """
        Given:
            invalid arguments for bulk update command

        When:
            running mongodb-bulk-update command in XSOAR

        Then:
            parse_and_validate_bulk_update_arguments will raise an error
        """
        with pytest.raises(DemistoException) as e:
            parse_and_validate_bulk_update_arguments(filter, update)
            assert error_message in str(e.value)

    def test_bulk_update_command(self, mocker, client=client, case_simple_bulk_update_args=case_simple_bulk_update_args):
        """
        Given:
            valid arguments for bulk update command

        When:
            running mongodb-bulk-update command in XSOAR

        Then:
            the expected human readable is returned
        """
        response = MockResponse(acknowledged=True, modified_count=1, upserted_count=1)
        mocker.patch.object(client, "bulk_update_entries", return_value=response)
        return_value = bulk_update_command(
            client, "test_collection", filter=case_simple_bulk_update_args[0], update=case_simple_bulk_update_args[1]
        )
        excepted_output = "MongoDB: Total of 1 entries has been modified.\
            \nMongoDB: Total of 1 entries has been inserted."
        # 'replace' method is used due to inconsistent spaces in the output
        assert return_value[0].replace(" ", "") == excepted_output.replace(" ", "")


def test_client_initialization_success():
    with patch("MongoDB.MongoClient") as mock_mongo_client:
        mock_db = MagicMock()
        mock_mongo_client.return_value.get_database.return_value = mock_db

        client = Client(
            urls=["mongodb://localhost:27017"],
            username="testuser",
            password="testpass",
            database="testdb",
            ssl=True,
            insecure=True,
            auth_source="admin",
            timeout=3000,
        )

        assert client.db == mock_db
        mock_mongo_client.assert_called_once_with(
            host=["mongodb://localhost:27017"],
            username="testuser",
            password="testpass",
            ssl=True,
            socketTimeoutMS=3000,
            tlsAllowInvalidCertificates=True,
            authSource="admin",
        )


def test_client_initialization_insecure_without_ssl():
    with pytest.raises(DemistoException) as e:
        Client(
            urls=["mongodb://localhost:27017"],
            username="testuser",
            password="testpass",
            database="testdb",
            ssl=False,
            insecure=True,
        )

    assert e.value.args[0] == '"Trust any certificate (not secure)" must be ticked with "Use TLS/SSL secured connection"'


def test_client_initialization_without_auth_source():
    with patch("MongoDB.MongoClient") as mock_mongo_client:
        Client(urls=["mongodb://localhost:27017"], username="testuser", password="testpass", database="testdb", ssl=True)

        mock_mongo_client.assert_called_once()
        assert "authSource" not in mock_mongo_client.call_args[1]