Repository: ansible-collections/community.general

Issue:
Slack: When Message contains "ts" module attempts to send as chat.update rather than post.message
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->
I have a word in my message that is being sent that contains "ts" (exists). When calling that message the module is attempting to send post messages to the update URI instead of the message post URL. This is due to a search string of `"ts" in payload`. Where payload is a json string representation of the payload. This should probably get moved to jsonify after doing any checks.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
slack

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
2.9.12
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below

```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
AWX/Tower

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
1. Create a message to Slack that involves the word exists
2. Send through the Slack module
3. Get error that invalid arguments
4. Check with Slack support and they are seeing update messages rather than post messages.


<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
Expect that the message will post

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
Error from Slack API

<!--- Paste verbatim command output between quotes -->
```paste below

```



Discussion:
Files identified in the description:
* [`plugins/modules/notification/slack.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/notification/slack.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @ramondelafuente
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
CC @anlutro

Resolving patch:
diff --git a/plugins/modules/notification/slack.py b/plugins/modules/notification/slack.py
--- a/plugins/modules/notification/slack.py
+++ b/plugins/modules/notification/slack.py
@@ -328,7 +328,6 @@ def build_payload_for_slack(module, text, channel, thread_id, username, icon_url
         ]
         payload['blocks'] = recursive_escape_quotes(blocks, block_keys_to_escape)
 
-    payload = module.jsonify(payload)
     return payload
 
 
@@ -377,14 +376,15 @@ def do_notify_slack(module, domain, token, payload):
     if use_webapi:
         headers['Authorization'] = 'Bearer ' + token
 
-    response, info = fetch_url(module=module, url=slack_uri, headers=headers, method='POST', data=payload)
+    data = module.jsonify(payload)
+    response, info = fetch_url(module=module, url=slack_uri, headers=headers, method='POST', data=data)
 
     if info['status'] != 200:
         if use_webapi:
             obscured_incoming_webhook = slack_uri
         else:
             obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')
-        module.fail_json(msg=" failed to send %s to %s: %s" % (payload, obscured_incoming_webhook, info['msg']))
+        module.fail_json(msg=" failed to send %s to %s: %s" % (data, obscured_incoming_webhook, info['msg']))
 
     # each API requires different handling
     if use_webapi:
@@ -459,8 +459,10 @@ def main():
     if 'ok' in slack_response:
         # Evaluate WebAPI response
         if slack_response['ok']:
+            # return payload as a string for backwards compatibility
+            payload_json = module.jsonify(payload)
             module.exit_json(changed=changed, ts=slack_response['ts'], channel=slack_response['channel'],
-                             api=slack_response, payload=payload)
+                             api=slack_response, payload=payload_json)
         else:
             module.fail_json(msg="Slack API error", error=slack_response['error'])
     else:


Test patch:
diff --git a/tests/unit/plugins/modules/notification/test_slack.py b/tests/unit/plugins/modules/notification/test_slack.py
--- a/tests/unit/plugins/modules/notification/test_slack.py
+++ b/tests/unit/plugins/modules/notification/test_slack.py
@@ -5,7 +5,7 @@
 
 import json
 import pytest
-from ansible_collections.community.general.tests.unit.compat.mock import patch
+from ansible_collections.community.general.tests.unit.compat.mock import Mock, patch
 from ansible_collections.community.general.plugins.modules.notification import slack
 from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
 
@@ -88,6 +88,45 @@ def test_message_with_thread(self):
             assert call_data['thread_ts'] == '100.00'
             assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
 
+    # https://github.com/ansible-collections/community.general/issues/1097
+    def test_ts_in_message_does_not_cause_edit(self):
+        set_module_args({
+            'token': 'xoxa-123456789abcdef',
+            'msg': 'test with ts'
+        })
+
+        with patch.object(slack, "fetch_url") as fetch_url_mock:
+            mock_response = Mock()
+            mock_response.read.return_value = '{"fake":"data"}'
+            fetch_url_mock.return_value = (mock_response, {"status": 200})
+            with self.assertRaises(AnsibleExitJson):
+                self.module.main()
+
+            self.assertTrue(fetch_url_mock.call_count, 1)
+            self.assertEquals(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.postMessage")
+
+    def test_edit_message(self):
+        set_module_args({
+            'token': 'xoxa-123456789abcdef',
+            'msg': 'test2',
+            'message_id': '12345'
+        })
+
+        with patch.object(slack, "fetch_url") as fetch_url_mock:
+            mock_response = Mock()
+            mock_response.read.return_value = '{"messages":[{"ts":"12345","msg":"test1"}]}'
+            fetch_url_mock.side_effect = [
+                (mock_response, {"status": 200}),
+                (mock_response, {"status": 200}),
+            ]
+            with self.assertRaises(AnsibleExitJson):
+                self.module.main()
+
+            self.assertTrue(fetch_url_mock.call_count, 2)
+            self.assertEquals(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.update")
+            call_data = json.loads(fetch_url_mock.call_args[1]['data'])
+            self.assertEquals(call_data['ts'], "12345")
+
     def test_message_with_blocks(self):
         """tests sending a message with blocks"""
         set_module_args({