arcade-mcp/toolkits/reddit/tests/test_utils.py
Eric Gustin 7c6a739f25
Add Reddit Toolkit (#336)
| Name | Description | Package | Version |

|----------------------------|------------------------------------------------------------------|---------|---------|
| Reddit.SubmitTextPost | Submit a text-based post to a subreddit |
Reddit | 0.0.1 |
| Reddit.CommentOnPost | Comment on a Reddit post | Reddit | 0.0.1 |
| Reddit.ReplyToComment | Reply to a Reddit comment | Reddit | 0.0.1 |
| Reddit.GetPostsInSubreddit | Gets posts titles, links, and other
metadata in the specified subreddit | Reddit | 0.0.1 |
| Reddit.GetContentOfPost | Get the content (body) of a Reddit post by
its identifier. | Reddit | 0.0.1 |
| Reddit.GetContentOfMultiplePosts | Get the content (body) of multiple
Reddit posts by their identifiers. | Reddit | 0.0.1 |
| Reddit.GetTopLevelComments | Get the first page of top-level comments
of a Reddit post. | Reddit | 0.0.1 |


### Why not use an SDK?
Reddit API does not have an official SDK, although
[PRAW](https://github.com/praw-dev/praw) has large community support.

I played around with PRAW, but ultimately decided to not use an SDK.
PRAW made it incredibly easy to work with Reddit Objects, but there were
a few drawbacks that ultimately swayed me to not use it:
1. PRAW assumes that it will do the auth for you. A client ID and secret
must be passed to PRAW, but a tool only has the auth token. I was able
to hack around this by manipulating private properties - but it felt too
hacky
2. PRAW does not support Python 3.13
3. PRAW is not async. There is
[AsyncPRAW](https://github.com/praw-dev/asyncpraw), but the community
does not look active there.
2025-04-03 14:07:10 -07:00

191 lines
6.4 KiB
Python

import pytest
from arcade.sdk.errors import ToolExecutionError
from arcade_reddit.utils import (
create_fullname_for_comment,
create_fullname_for_multiple_posts,
create_fullname_for_post,
create_path_for_post,
parse_get_content_of_multiple_posts_response,
parse_get_content_of_post_response,
parse_get_posts_in_subreddit_response,
parse_get_top_level_comments_response,
)
@pytest.mark.parametrize(
"identifier, expected",
[
("abcdef", "t1_abcdef"),
("https://www.reddit.com/r/test/comments/1234567890/comment/1abcdef/", "t1_1abcdef"),
("t1_abcdef", "t1_abcdef"),
("/r/test/comments/1234567890/comment/1abcdef/", "t1_1abcdef"),
("not-an-id", pytest.raises(ToolExecutionError)),
# Fullname: Invalid (not a Reddit comment id, but a Reddit post id)
("t2_abcdef", pytest.raises(ToolExecutionError)),
# URL: Invalid (not a Reddit url to a comment, but to a post)
("https://www.reddit.com/r/test/comments/1234567890", pytest.raises(ToolExecutionError)),
# Permalink: Invalid (not a Reddit permalink to a comment, but to a post)
("/r/test/comments/1234567890", pytest.raises(ToolExecutionError)),
],
)
def test_create_fullname_for_comment(identifier, expected) -> None:
if isinstance(expected, str):
assert create_fullname_for_comment(identifier) == expected
else:
with expected:
create_fullname_for_comment(identifier)
@pytest.mark.parametrize(
"identifier, expected",
[
("https://www.reddit.com/r/test/comments/1234abc/", "/r/test/comments/1234abc/"),
("1234abc", "/comments/1234abc"),
("/r/test/comments/1234abc/", "/r/test/comments/1234abc/"),
("t3_1234abc", "/comments/1234abc"),
# URL: invalid (non-reddit domain)
("https://www.example.com/r/test/comments/1234abc/", pytest.raises(ToolExecutionError)),
# Post ID: invalid (non-alphanumeric character)
("12!abc", pytest.raises(ToolExecutionError)),
# Permalink: invalid (missing the leading "/" so not recognized as a proper permalink)
("r/test/comments/1234abc/", pytest.raises(ToolExecutionError)),
# Fullname: invalid (contains an illegal character)
("t3_1234*abc", pytest.raises(ToolExecutionError)),
],
)
def test_create_path_for_post(identifier, expected):
if isinstance(expected, str):
result = create_path_for_post(identifier)
assert result == expected
else:
with expected:
create_path_for_post(identifier)
@pytest.mark.parametrize(
"identifier, expected",
[
("https://www.reddit.com/r/test/comments/1234abc/", "t3_1234abc"),
("1234abc", "t3_1234abc"),
("/r/test/comments/1234abc/", "t3_1234abc"),
("t3_1234abc", "t3_1234abc"),
# URL: invalid (missing "/comments/" segment)
("https://www.reddit.com/r/test/1234abc/", pytest.raises(ToolExecutionError)),
# Post ID: invalid (non-alphanumeric)
("12!abc", pytest.raises(ToolExecutionError)),
# Permalink: invalid (missing "/comments/" segment)
("/r/test/1234abc/", pytest.raises(ToolExecutionError)),
# Fullname: invalid (type prefix is for a message, not a post
("t4_1234abc", pytest.raises(ToolExecutionError)),
],
)
def test_create_fullname_for_post(identifier, expected):
if isinstance(expected, str):
result = create_fullname_for_post(identifier)
assert result == expected
else:
with expected:
create_fullname_for_post(identifier)
@pytest.mark.parametrize(
"identifiers, expected_fullnames, expected_warnings",
[
([], [], []),
(
["t3_1234abc", "https://www.reddit.com/r/test/comments/1234abc/"],
["t3_1234abc", "t3_1234abc"],
[],
),
(
["t3_1234abc", "not-a-valid-identifier"],
["t3_1234abc"],
[
{
"message": "'not-a-valid-identifier' is not a valid Reddit post identifier.",
"identifier": "not-a-valid-identifier",
}
],
),
(
["inv@lid", "not-a-valid-identifier"],
[],
[
{
"message": "'inv@lid' is not a valid Reddit post identifier.",
"identifier": "inv@lid",
},
{
"message": "'not-a-valid-identifier' is not a valid Reddit post identifier.",
"identifier": "not-a-valid-identifier",
},
],
),
],
)
def test_create_fullname_for_multiple_posts(identifiers, expected_fullnames, expected_warnings):
actual_fullnames, actual_warnings = create_fullname_for_multiple_posts(identifiers)
assert actual_fullnames == expected_fullnames
assert actual_warnings == expected_warnings
def test_parse_get_posts_in_subreddit_response_empty():
data = {}
expected = {"cursor": None, "posts": []}
result = parse_get_posts_in_subreddit_response(data)
assert result == expected
def test_parse_get_content_of_post_response_empty_and_malformed():
data = []
result = parse_get_content_of_post_response(data)
assert result == {}
data = None
result = parse_get_content_of_post_response(data)
assert result == {}
# missing expected keys
data = [{}]
expected = {
"id": None,
"name": None,
"title": None,
"author": None,
"subreddit": None,
"created_utc": None,
"num_comments": None,
"score": None,
"upvote_ratio": None,
"upvotes": None,
"permalink": None,
"url": None,
"is_video": None,
"body": None,
}
result = parse_get_content_of_post_response(data)
assert result == expected
def test_parse_get_content_of_multiple_posts_response_empty_and_malformed():
expected = []
data = {}
result = parse_get_content_of_multiple_posts_response(data)
assert result == expected
data = None
result = parse_get_content_of_multiple_posts_response(data)
assert result == expected
data = [{}]
result = parse_get_content_of_multiple_posts_response(data)
assert result == expected
def test_parse_get_top_level_comments_response_missing_data():
data = [{}]
expected = {"comments": [], "num_comments": 0}
result = parse_get_top_level_comments_response(data)
assert result == expected