Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make WebhookClient (sync/async) #send method accept link unfurl params #1047

Merged
merged 18 commits into from Jun 28, 2021

Conversation

srajiang
Copy link
Member

Summary

Resolves #1045, and addresses a bug where bool values being converted to "0" and "1" were not correctly being interpreted by Slack API.

Changes:

  • Modifies WebhookClient#send and AsyncWebhookClient#send to accept unfurl_links and unfurl_media as optional arguments)
  • Previously booleans False and True were being converted to "0" and "1" in the outgoing http request body, but the options were not correctly being applied. See "recreate bug" section below. I believe this is due to the value being returned as a string. With a small modification to _to_0_or_1_if_bool() method to return integer instead, the unfurl options provided are correctly applied.

To recreate the bug:

Using the #send_dict method only, you can recreate this with

dict = {
   "text": ""<https://imgs.xkcd.com/comics/new_sports_system_2x.png>", "unfurl_links": False, "unfurl_media": False
}

response = webhook.send_dict(dict)
assert response.status_code == 200
assert response.body == "ok"

In this case, outgoing http body looks like:

{"text": "hello webhook world\n<https://imgs.xkcd.com/comics/new_sports_system_2x.png>", "unfurl_links": "0", "unfurl_media": "0"}

Per docs, example, this link should be prevented from unfurling, but it does unfurl.

With the changes, the outgoing http body looks like:

{"text": "hello webhook world\n<https://imgs.xkcd.com/comics/new_sports_system_2x.png>", "unfurl_links": 0, "unfurl_media": 0}

And the link is properly prevented from unfurling.

Category (place an x in each of the [ ])

  • slack_sdk.web.WebClient (sync/async) (Web API client)
  • slack_sdk.webhook.WebhookClient (sync/async) (Incoming Webhook, response_url sender)
  • slack_sdk.socket_mode (Socket Mode client)
  • slack_sdk.signature (Request Signature Verifier)
  • slack_sdk.oauth (OAuth Flow Utilities)
  • slack_sdk.models (UI component builders)
  • slack_sdk.scim (SCIM API client)
  • slack_sdk.audit_logs (Audit Logs API client)
  • slack_sdk.rtm_v2 (RTM client)
  • /docs-src (Documents, have you run ./docs.sh?)
  • /docs-src-v2 (Documents, have you run ./docs-v2.sh?)
  • /tutorial (PythOnBoardingBot tutorial)
  • tests/integration_tests (Automated tests for this library)

Requirements (place an x in each [ ])

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh after making the changes. <---- Ran up until test_interactions_websocket_client.py and then hung again

@gitwave gitwave bot added the untriaged label Jun 24, 2021
@srajiang srajiang requested a review from seratch June 24, 2021 01:11
@codecov
Copy link

codecov bot commented Jun 24, 2021

Codecov Report

Merging #1047 (d410e8d) into main (a087d71) will decrease coverage by 0.00%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1047      +/-   ##
==========================================
- Coverage   84.33%   84.33%   -0.01%     
==========================================
  Files          95       95              
  Lines        8938     8937       -1     
==========================================
- Hits         7538     7537       -1     
  Misses       1400     1400              
Impacted Files Coverage Δ
slack_sdk/webhook/async_client.py 93.65% <ø> (ø)
slack_sdk/webhook/client.py 94.44% <ø> (ø)
slack_sdk/webhook/internal_utils.py 82.60% <ø> (-0.73%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a087d71...d410e8d. Read the comment docs.

@seratch seratch added enhancement M-T: A feature request for new functionality Version: 3x web-client and removed untriaged labels Jun 24, 2021
@seratch seratch added this to the 3.8.0 milestone Jun 24, 2021
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -229,7 +229,7 @@ def _next_cursor_is_present(data) -> bool:

def _to_0_or_1_if_bool(v: Any) -> Union[Any, str]:
if isinstance(v, bool):
return "1" if v else "0"
return 1 if v else 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, this change is fine for WebClient etc. but I will check the behavior later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As WebookClient sends content-type: application/json data, we don't necessarily convert boolean values to 1/0. Can you modify slack_sdk/webhook/internal_utils.py this way instead?

diff --git a/slack_sdk/webhook/internal_utils.py b/slack_sdk/webhook/internal_utils.py
index 37def28..091c06c 100644
--- a/slack_sdk/webhook/internal_utils.py
+++ b/slack_sdk/webhook/internal_utils.py
@@ -12,7 +12,6 @@ from .webhook_response import WebhookResponse
 def _build_body(original_body: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
     if original_body:
         body = {k: v for k, v in original_body.items() if v is not None}
-        body = convert_bool_to_0_or_1(body)
         _parse_web_class_objects(body)
         return body
     return None

Copy link
Member Author

@srajiang srajiang Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seratch. Okay, I see that changing it this way reduces the number of possible affected surfaces. Thanks! I have also reverted the above line to return strings instead of integers. Feel free to resolve this, if these changes are what you had intended!

slack_sdk/web/internal_utils.py Outdated Show resolved Hide resolved
slack_sdk/webhook/__init__.py Outdated Show resolved Hide resolved
@seratch
Copy link
Member

seratch commented Jun 24, 2021

Ran up until test_interactions_websocket_client.py and then hung again

Hmm, this test basically takes much longer than others (it verifies many patters of data chunk size). You can see the progress in logs/pytest.log. Also, for convenience, you can run specific tests by ./scripts/run_unit_tests.sh tests/slack_sdk/webhook/.

self.assertEqual(200, response.status_code)
self.assertEqual("ok", response.body)

def __get_channel_id(self):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For review: I needed the functionality of pulling channel ID in the two new tests. As this would be repeated code, I have pulled the logic into separate function and use in the test_webhook, test_with_unfurls_off and test_with_unfurls_on methods where needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do the same in the setUp method and use self.channel_id in test methods. If you use token / client in test methods, setting them to the instance fields would be also fine.

diff --git a/integration_tests/webhook/test_webhook.py b/integration_tests/webhook/test_webhook.py
index 12f60d0..82d0b6f 100644
--- a/integration_tests/webhook/test_webhook.py
+++ b/integration_tests/webhook/test_webhook.py
@@ -16,7 +16,20 @@ from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTe
 
 class TestWebhook(unittest.TestCase):
     def setUp(self):
-        pass
+        if not hasattr(self, "channel_id"):
+            token = os.environ[SLACK_SDK_TEST_BOT_TOKEN]
+            channel_name = os.environ[SLACK_SDK_TEST_INCOMING_WEBHOOK_CHANNEL_NAME].replace(
+                "#", ""
+            )
+            client = WebClient(token=token)
+            self.channel_id = None
+            for resp in client.conversations_list(limit=10):
+                for c in resp["channels"]:
+                    if c["name"] == channel_name:
+                        self.channel_id = c["id"]
+                        break
+                if self.channel_id is not None:
+                    break
 
     def tearDown(self):
         pass

@srajiang
Copy link
Member Author

srajiang commented Jun 24, 2021

@seratch I've put some questions in alongside my latest changes (which add to the sync webhook's integration tests). Integration tests feel more appropriate for the behavior described than unit tests, a we cannot determine whether an unfurl options was applied correctly without also knowing what the Slack API is doing.

Let me know what you think about that, and whether there's behavior at the unit level that you think would make sense to test in this case. And based on your feedback to my questions on the changes, I will go ahead an implement for async as well. Thanks in advance for taking a look!

@srajiang srajiang requested a review from seratch June 24, 2021 21:03
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srajiang Thanks for the updates. Really nice!

Can you add some unit tests as well?

As you may be aware, only the integration tests can verify the actual behaviors in this PR but they are not part of the CI builds at the moment. We want to have something that may detect very basic level regression for safety and for slightly better code coverage in the CI builds.

The unit tests do not need to have unfurling-specific assertions. Verifying the method calls with the new arguments does not cause any errors with the mock HTTP server and any runtime errors is enough.

self.assertEqual(200, response.status_code)
self.assertEqual("ok", response.body)

def __get_channel_id(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do the same in the setUp method and use self.channel_id in test methods. If you use token / client in test methods, setting them to the instance fields would be also fine.

diff --git a/integration_tests/webhook/test_webhook.py b/integration_tests/webhook/test_webhook.py
index 12f60d0..82d0b6f 100644
--- a/integration_tests/webhook/test_webhook.py
+++ b/integration_tests/webhook/test_webhook.py
@@ -16,7 +16,20 @@ from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTe
 
 class TestWebhook(unittest.TestCase):
     def setUp(self):
-        pass
+        if not hasattr(self, "channel_id"):
+            token = os.environ[SLACK_SDK_TEST_BOT_TOKEN]
+            channel_name = os.environ[SLACK_SDK_TEST_INCOMING_WEBHOOK_CHANNEL_NAME].replace(
+                "#", ""
+            )
+            client = WebClient(token=token)
+            self.channel_id = None
+            for resp in client.conversations_list(limit=10):
+                for c in resp["channels"]:
+                    if c["name"] == channel_name:
+                        self.channel_id = c["id"]
+                        break
+                if self.channel_id is not None:
+                    break
 
     def tearDown(self):
         pass

integration_tests/webhook/test_webhook.py Show resolved Hide resolved
integration_tests/webhook/test_webhook.py Show resolved Hide resolved
integration_tests/webhook/test_webhook.py Show resolved Hide resolved
@srajiang
Copy link
Member Author

@seratch Yes, the level of expected behavior for unit-level tests you describe makes sense to me, and I have added them alongside the integration tests. It also makes more sense to me to add the channel_id logic to setUp(). Thanks for the detailed feedback!

@srajiang srajiang requested a review from seratch June 28, 2021 21:06
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@seratch seratch merged commit 3422a23 into slackapi:main Jun 28, 2021
@srajiang srajiang deleted the sj_update_webhook_clients branch August 9, 2021 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement M-T: A feature request for new functionality Version: 3x web-client
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add unfurl_links / unfurl_media to WebhookClient#send method
2 participants