Skip to content

Commit

Permalink
feat: twitter langchain actions tests (#53)
Browse files Browse the repository at this point in the history
* first pass implementing tests for twitter langchain

* formatting

* implementing feedback
  • Loading branch information
stat authored Nov 20, 2024
1 parent e9d5910 commit 222f205
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def account_details(client: tweepy.Client) -> str:

message = f"""Successfully retrieved authenticated user account details:\n{dumps(response)}"""
except tweepy.errors.TweepyException as e:
message = f"Error retrieving authenticated user account details: {e}"
message = f"Error retrieving authenticated user account details:\n{e}"

return message

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
This tool will post a reply to a tweet on Twitter. The tool takes the text of the reply and the tweet id to reply to as input. Tweets can be maximum 280 characters.
A successful response will return a message with the api response as a json payload:
{"data": {"id": "0123456789012345678", "text": "hellllloooo!", "edit_history_tweet_ids": ["1234567890123456789"]}}
{"data": {"id": "0123456789012345678", "text": "So good to be here!", "edit_history_tweet_ids": ["1234567890123456789"]}}
A failure response will return a message with the tweepy client api request error:
You are not allowed to create a Tweet with duplicate content.
Expand Down Expand Up @@ -47,7 +47,7 @@ def post_tweet_reply(client: tweepy.Client, tweet_id: str, tweet_reply: str) ->
message = ""

try:
response = client.create_tweet(text=tweet_reply, in_reply_to_tweet_id=tweet_id)
response = client.create_tweet(in_reply_to_tweet_id=tweet_id, text=tweet_reply)
message = f"Successfully posted reply to Twitter:\n{dumps(response)}"
except tweepy.errors.TweepyException as e:
message = f"Error posting reply to Twitter:\n{e}"
Expand Down
1 change: 0 additions & 1 deletion twitter-langchain/examples/chatbot/chatbot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import sys
import time

Expand Down
Empty file.
Empty file.
44 changes: 44 additions & 0 deletions twitter-langchain/tests/actions/test_account_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from json import dumps
from unittest.mock import patch

import tweepy
from cdp_agentkit_core.actions.social.twitter.account_details import (
account_details,
)

MOCK_ID = 1234
MOCK_NAME = "Test Account"
MOCK_USERNAME = "testaccount"


def test_account_details_success(tweepy_factory):
mock_client = tweepy_factory()
mock_client_result = {
"data": {
"id": MOCK_ID,
"name": MOCK_USERNAME,
"username": MOCK_USERNAME,
},
}

expected_result = mock_client_result.copy()
expected_result["data"]["url"] = f"https://x.com/{MOCK_USERNAME}"
expected_response = f"Successfully retrieved authenticated user account details:\n{dumps(expected_result)}"

with patch.object(mock_client, "get_me", return_value=mock_client_result) as mock_get_me:
response = account_details(mock_client)

assert response == expected_response
mock_get_me.assert_called_once_with()


def test_account_details_failure(tweepy_factory):
mock_client = tweepy_factory()

expected_result = tweepy.errors.TweepyException("Tweepy Error")
expected_response = f"Error retrieving authenticated user account details:\n{expected_result}"

with patch.object(mock_client, "get_me", side_effect=expected_result) as mock_tweepy_get_me:
response = account_details(mock_client)

assert response == expected_response
60 changes: 60 additions & 0 deletions twitter-langchain/tests/actions/test_account_mentions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from json import dumps
from unittest.mock import patch

import pytest
import tweepy
from cdp_agentkit_core.actions.social.twitter.account_mentions import (
AccountMentionsInput,
account_mentions,
)

MOCK_ACCOUNT_ID = "1234"

def test_account_mentions_input_model_valid():
"""Test that AccountMentionsInput accepts valid parameters."""
input_model = AccountMentionsInput(
account_id=MOCK_ACCOUNT_ID,
)

assert input_model.account_id == MOCK_ACCOUNT_ID


def test_account_mentions_input_model_missing_params():
"""Test that AccountMentionsInput raises error when params are missing."""
with pytest.raises(ValueError):
AccountMentionsInput()

def test_account_mentions_success(tweepy_factory):
mock_client = tweepy_factory()
mock_client_result = {
"data": [
{
"id": "1857479287504584856",
"edit_history_tweet_ids": ["1857479287504584856"],
"text": "@CDPAgentKit reply",
},
],
"meta": {
"result_count": 1,
"newest_id": "1857479287504584856",
"oldest_id": "1857479287504584856",
},
}

expected_response = f"Successfully retrieved authenticated user account mentions:\n{dumps(mock_client_result)}"

with patch.object(mock_client, "get_users_mentions", return_value=mock_client_result) as mock_get_users_mentions:
response = account_mentions(mock_client, MOCK_ACCOUNT_ID)

assert response == expected_response
mock_get_users_mentions.assert_called_once_with(MOCK_ACCOUNT_ID)

def test_account_mentions_failure(tweepy_factory):
mock_client = tweepy_factory()

expected_result = tweepy.errors.TweepyException("Tweepy Error")
expected_response = f"Error retrieving authenticated user account mentions:\n{expected_result}"

with patch.object(mock_client, "get_users_mentions", side_effect=expected_result) as mock_get_users_mentions:
response = account_mentions(mock_client, MOCK_ACCOUNT_ID)
assert response == expected_response
56 changes: 56 additions & 0 deletions twitter-langchain/tests/actions/test_post_tweet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from json import dumps
from unittest.mock import patch

import pytest
import tweepy
from cdp_agentkit_core.actions.social.twitter.post_tweet import (
PostTweetInput,
post_tweet,
)

MOCK_TWEET = "hello, world!"

def test_post_tweet_input_model_valid():
"""Test that PostTweetInput accepts valid parameters."""
input_model = PostTweetInput(
tweet=MOCK_TWEET,
)

assert input_model.tweet == MOCK_TWEET


def test_post_tweet_input_model_missing_params():
"""Test that PostTweetInput raises error when params are missing."""
with pytest.raises(ValueError):
PostTweetInput()

def test_post_tweet_success(tweepy_factory):
mock_client = tweepy_factory()
mock_client_result = {
"data": {
"text": "hello, world!",
"id": "0123456789012345678",
"edit_history_tweet_ids": ["0123456789012345678"],
}
}

expected_response = f"Successfully posted to Twitter:\n{dumps(mock_client_result)}"

with patch.object(mock_client, "create_tweet", return_value=mock_client_result) as mock_create_tweet:
response = post_tweet(mock_client, MOCK_TWEET)

assert response == expected_response
mock_create_tweet.assert_called_once_with(text=MOCK_TWEET)


def test_post_tweet_failure(tweepy_factory):
mock_client = tweepy_factory()

expected_result = tweepy.errors.TweepyException("Tweepy Error")
expected_response = f"Error posting to Twitter:\n{expected_result}"

with patch.object(mock_client, "create_tweet", side_effect=expected_result) as mock_tweepy_create_tweet:
response = post_tweet(mock_client, MOCK_TWEET)
assert response == expected_response


62 changes: 62 additions & 0 deletions twitter-langchain/tests/actions/test_post_tweet_reply.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from json import dumps
from unittest.mock import patch

import pytest
import tweepy
from cdp_agentkit_core.actions.social.twitter.post_tweet_reply import (
PostTweetReplyInput,
post_tweet_reply,
)

MOCK_TWEET_ID = "1234"
MOCK_TWEET_REPLY = "So good to be here!"

def test_post_tweet_input_model_valid():
"""Test that PostTweetReplyInput accepts valid parameters."""
input_model = PostTweetReplyInput(
tweet_id=MOCK_TWEET_ID,
tweet_reply=MOCK_TWEET_REPLY,
)

assert input_model.tweet_id == MOCK_TWEET_ID
assert input_model.tweet_reply == MOCK_TWEET_REPLY


def test_post_tweet_input_model_missing_params():
"""Test that PostTweetReplyInput raises error when params are missing."""
with pytest.raises(ValueError):
PostTweetReplyInput()

def test_post_tweet_success(tweepy_factory):
mock_client = tweepy_factory()
mock_client_result = {
"data": {
"id": "0123456789012345678",
"text": "So good to be here!",
"edit_history_tweet_ids": ["1234567890123456789"],
}
}

expected_response = f"Successfully posted reply to Twitter:\n{dumps(mock_client_result)}"

with patch.object(mock_client, "create_tweet", return_value=mock_client_result) as mock_create_tweet:
response = post_tweet_reply(mock_client, MOCK_TWEET_ID, MOCK_TWEET_REPLY)

assert response == expected_response
mock_create_tweet.assert_called_once_with(
in_reply_to_tweet_id=MOCK_TWEET_ID,
text=MOCK_TWEET_REPLY,
)


def test_post_tweet_failure(tweepy_factory):
mock_client = tweepy_factory()

expected_result = tweepy.errors.TweepyException("Tweepy Error")
expected_response = f"Error posting reply to Twitter:\n{expected_result}"

with patch.object(mock_client, "create_tweet", side_effect=expected_result) as mock_tweepy_create_tweet:
response = post_tweet_reply(mock_client, MOCK_TWEET_ID, MOCK_TWEET_REPLY)
assert response == expected_response


7 changes: 7 additions & 0 deletions twitter-langchain/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

factory_modules = [
f[:-3] for f in os.listdir("./tests/factories") if f.endswith(".py") and f != "__init__.py"
]

pytest_plugins = [f"tests.factories.{module_name}" for module_name in factory_modules]
15 changes: 15 additions & 0 deletions twitter-langchain/tests/factories/twitter_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from unittest.mock import MagicMock

import pytest
import tweepy


@pytest.fixture
def tweepy_factory():
"""Create and return a factory for tweepy mock fixtures."""

def _create_tweepy():
tweepy_mock = MagicMock(spec=tweepy.Client)
return tweepy_mock

return _create_tweepy

0 comments on commit 222f205

Please sign in to comment.