salt/tests/pytests/unit/engines/test_slack_bolt_engine.py

516 lines
17 KiB
Python

"""
unit tests for the slack engine
"""
import pytest
import salt.config
import salt.engines.slack_bolt_engine as slack_bolt_engine
from tests.support.mock import MagicMock, call, patch
pytestmark = [
pytest.mark.skipif(
slack_bolt_engine.HAS_SLACKBOLT is False,
reason="The slack_bolt is not installed",
)
]
class MockRunnerClient:
"""
Mock RunnerClient class
"""
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def asynchronous(self, *args, **kwargs):
"""
Mock asynchronous method
"""
return True
class MockLocalClient:
"""
Mock RunnerClient class
"""
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def __enter__(self, *args, **kwargs):
return self
def __exit__(self, *args, **kwargs):
pass
def cmd_async(self, *args, **kwargs):
"""
Mock cmd_async method
"""
return True
class MockSlackBoltSocketMode:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def connect(self, *args, **kwargs):
return True
class MockSlackBoltApp:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.client = MockSlackBoltAppClient()
self.logger = None
self.proxy = None
def message(self, *args, **kwargs):
return MagicMock(return_value=True)
class MockSlackBoltAppClient:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def chat_postMessage(self, *args, **kwargs):
return MagicMock(return_value=True)
def files_upload(self, *args, **kwargs):
return MagicMock(return_value=True)
@pytest.fixture
def configure_loader_modules():
return {slack_bolt_engine: {}}
@pytest.fixture
def slack_client(minion_opts):
mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
app_token = "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
bot_token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
trigger = "!"
with patch.dict(slack_bolt_engine.__opts__, minion_opts):
with patch(
"slack_bolt.App", MagicMock(autospec=True, return_value=MockSlackBoltApp())
):
with patch(
"slack_bolt.adapter.socket_mode.SocketModeHandler",
MagicMock(autospec=True, return_value=MockSlackBoltSocketMode()),
):
slack_client = slack_bolt_engine.SlackClient(
app_token, bot_token, trigger
)
yield slack_client
def test_control_message_target(slack_client):
"""
Test slack engine: control_message_target
"""
trigger_string = "!"
loaded_groups = {
"default": {
"targets": {},
"commands": {"cmd.run", "test.ping"},
"default_target": {"tgt_type": "glob", "target": "*"},
"users": {"gareth"},
"aliases": {
"whoami": {"cmd": "cmd.run whoami"},
"list_pillar": {"cmd": "pillar.items"},
},
}
}
slack_user_name = "gareth"
# Check for correct cmdline
_expected = (True, {"tgt_type": "glob", "target": "*"}, ["cmd.run", "whoami"])
text = "!cmd.run whoami"
target_commandline = slack_client.control_message_target(
slack_user_name, text, loaded_groups, trigger_string
)
assert target_commandline == _expected
# Check aliases result in correct cmdline
text = "!whoami"
target_commandline = slack_client.control_message_target(
slack_user_name, text, loaded_groups, trigger_string
)
assert target_commandline == _expected
# Check pillar is overridden
_expected = (
True,
{"tgt_type": "glob", "target": "*"},
["pillar.items", 'pillar={"hello": "world"}'],
)
text = r"""!list_pillar pillar='{"hello": "world"}'"""
target_commandline = slack_client.control_message_target(
slack_user_name, text, loaded_groups, trigger_string
)
assert target_commandline == _expected
# Check target is overridden
_expected = (
True,
{"tgt_type": "glob", "target": "localhost"},
["cmd.run", "whoami"],
)
text = "!cmd.run whoami target='localhost'"
target_commandline = slack_client.control_message_target(
slack_user_name, text, loaded_groups, trigger_string
)
assert target_commandline == _expected
def test_run_commands_from_slack_async(slack_client):
"""
Test slack engine: test_run_commands_from_slack_async
"""
mock_job_status = {
"20221027001127600438": {
"data": {"minion": {"return": True, "retcode": 0, "success": True}},
"function": "test.ping",
}
}
message_generator = [
{
"message_data": {
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
"type": "message",
"text": '!test.ping target="minion"',
"user": "U02QY11UJ",
"ts": "1666829486.542159",
"blocks": [
{
"type": "rich_text",
"block_id": "2vdy",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": '!test.ping target="minion"',
}
],
}
],
}
],
"team": "T02QY11UG",
"channel": "C02QY11UQ",
"event_ts": "1666829486.542159",
"channel_type": "channel",
},
"channel": "C02QY11UQ",
"user": "U02QY11UJ",
"user_name": "garethgreenaway",
"cmdline": ["test.ping"],
"target": {"target": "minion", "tgt_type": "glob"},
}
]
mock_files_upload_resp = {
"ok": True,
"file": {
"id": "F047YTDGJF9",
"created": 1666883749,
"timestamp": 1666883749,
"name": "salt-results-20221027081549173603.yaml",
"title": "salt-results-20221027081549173603",
"mimetype": "text/plain",
"filetype": "yaml",
"pretty_type": "YAML",
"user": "U0485K894PN",
"user_team": "T02QY11UG",
"editable": True,
"size": 18,
"mode": "snippet",
"is_external": False,
"external_type": "",
"is_public": True,
"public_url_shared": False,
"display_as_bot": False,
"username": "",
"url_private": "",
"url_private_download": "",
"permalink": "",
"permalink_public": "",
"edit_link": "",
"preview": "minion:\n True",
"preview_highlight": "",
"lines": 2,
"lines_more": 0,
"preview_is_truncated": False,
"comments_count": 0,
"is_starred": False,
"shares": {
"public": {
"C02QY11UQ": [
{
"reply_users": [],
"reply_users_count": 0,
"reply_count": 0,
"ts": "1666883749.485979",
"channel_name": "general",
"team_id": "T02QY11UG",
"share_user_id": "U0485K894PN",
}
]
}
},
"channels": ["C02QY11UQ"],
"groups": [],
"ims": [],
"has_rich_preview": False,
"file_access": "visible",
},
}
patch_app_client_files_upload = patch.object(
MockSlackBoltAppClient,
"files_upload",
MagicMock(autospec=True, return_value=mock_files_upload_resp),
)
patch_app_client_chat_postMessage = patch.object(
MockSlackBoltAppClient,
"chat_postMessage",
MagicMock(autospec=True, return_value=True),
)
patch_slack_client_run_until = patch.object(
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
)
patch_slack_client_run_command_async = patch.object(
slack_client,
"run_command_async",
MagicMock(autospec=True, return_value="20221027001127600438"),
)
patch_slack_client_get_jobs_from_runner = patch.object(
slack_client,
"get_jobs_from_runner",
MagicMock(autospec=True, return_value=mock_job_status),
)
upload_calls = call(
channels="C02QY11UQ",
content="minion:\n True",
filename="salt-results-20221027090136014442.yaml",
)
chat_postMessage_calls = [
call(
channel="C02QY11UQ",
text="@garethgreenaway's job is submitted as salt jid 20221027001127600438",
),
call(
channel="C02QY11UQ",
text="@garethgreenaway's job `['test.ping']` (id: 20221027001127600438) (target: {'target': 'minion', 'tgt_type': 'glob'}) returned",
),
]
#
# test with control as True and fire_all as False
#
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
slack_client.run_commands_from_slack_async(
message_generator=message_generator,
fire_all=False,
tag="salt/engines/slack",
control=True,
)
app_client_files_upload.asser_has_calls(upload_calls)
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
#
# test with control and fire_all as True
#
patch_slack_client_run_until = patch.object(
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
)
mock_event_send = MagicMock(return_value=True)
patch_event_send = patch.dict(
slack_bolt_engine.__salt__, {"event.send": mock_event_send}
)
event_send_calls = [
call(
"salt/engines/slack/message",
{
"message_data": {
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
"type": "message",
"text": '!test.ping target="minion"',
"user": "U02QY11UJ",
"ts": "1666829486.542159",
"blocks": [
{
"type": "rich_text",
"block_id": "2vdy",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": '!test.ping target="minion"',
}
],
}
],
}
],
"team": "T02QY11UG",
"channel": "C02QY11UQ",
"event_ts": "1666829486.542159",
"channel_type": "channel",
},
"channel": "C02QY11UQ",
"user": "U02QY11UJ",
"user_name": "garethgreenaway",
"cmdline": ["test.ping"],
"target": {"target": "minion", "tgt_type": "glob"},
},
)
]
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_event_send, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
slack_client.run_commands_from_slack_async(
message_generator=message_generator,
fire_all=True,
tag="salt/engines/slack",
control=True,
)
app_client_files_upload.asser_has_calls(upload_calls)
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
mock_event_send.asser_has_calls(event_send_calls)
def test_run_command_async(slack_client):
"""
Test slack engine: test_run_command_async
"""
msg = {
"message_data": {
"client_msg_id": "6c71d7f9-a44d-402f-8f9f-d1bb5b650853",
"type": "message",
"text": '!test.ping target="minion"',
"user": "U02QY11UJ",
"ts": "1667427929.764169",
"blocks": [
{
"type": "rich_text",
"block_id": "AjL",
"elements": [
{
"type": "rich_text_section",
"elements": [
{"type": "text", "text": '!test.ping target="minion"'}
],
}
],
}
],
"team": "T02QY11UG",
"channel": "C02QY11UQ",
"event_ts": "1667427929.764169",
"channel_type": "channel",
},
"channel": "C02QY11UQ",
"user": "U02QY11UJ",
"user_name": "garethgreenaway",
"cmdline": ["test.ping"],
"target": {"target": "minion", "tgt_type": "glob"},
}
local_client_mock = MagicMock(autospec=True, return_value=MockLocalClient())
patch_local_client = patch("salt.client.LocalClient", local_client_mock)
local_client_cmd_async_mock = MagicMock(
autospec=True, return_value={"jid": "20221027001127600438"}
)
patch_local_client_cmd_async = patch.object(
MockLocalClient, "cmd_async", local_client_cmd_async_mock
)
expected_calls = [call("minion", "test.ping", arg=[], kwarg={}, tgt_type="glob")]
with patch_local_client, patch_local_client_cmd_async as local_client_cmd_async:
ret = slack_client.run_command_async(msg)
local_client_cmd_async.assert_has_calls(expected_calls)
msg = {
"message_data": {
"client_msg_id": "35f4783f-8913-4687-8f04-21182bcacd5a",
"type": "message",
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
"user": "U02QY11UJ",
"ts": "1667429460.576889",
"blocks": [
{
"type": "rich_text",
"block_id": "EAzTy",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
}
],
}
],
}
],
"team": "T02QY11UG",
"channel": "C02QY11UQ",
"event_ts": "1667429460.576889",
"channel_type": "channel",
},
"channel": "C02QY11UQ",
"user": "U02QY11UJ",
"user_name": "garethgreenaway",
"cmdline": ["test.arg", "arg1", "arg2", "arg3", "key1=value1", "key2=value2"],
"target": {"target": "*", "tgt_type": "glob"},
}
runner_client_mock = MagicMock(autospec=True, return_value=MockRunnerClient())
patch_runner_client = patch("salt.runner.RunnerClient", runner_client_mock)
runner_client_asynchronous_mock = MagicMock(
autospec=True, return_value={"jid": "20221027001127600438"}
)
patch_runner_client_asynchronous = patch.object(
MockRunnerClient, "asynchronous", runner_client_asynchronous_mock
)
expected_calls = [
call(
"test.arg",
{
"arg": ["arg1", "arg2", "arg3"],
"kwarg": {"key1": "value1", "key2": "value2"},
},
)
]
with patch_runner_client, patch_runner_client_asynchronous as runner_client_asynchronous:
ret = slack_client.run_command_async(msg)
runner_client_asynchronous.assert_has_calls(expected_calls)