diff --git a/test.py b/test.py new file mode 100644 index 00000000..0d5338c4 --- /dev/null +++ b/test.py @@ -0,0 +1,83 @@ +import topgg + +from sys import stdout +import asyncio +import os + +INDENTATION = 2 + + +def is_local(data: object) -> bool: + return getattr(data, '__module__', '').startswith('topgg') + + +def _test_attributes(obj: object, indent_level: int) -> None: + for name in getattr(obj.__class__, '__slots__', ()): + stdout.write(f'{" " * indent_level}{obj.__class__.__name__}.{name}') + data = getattr(obj, name) + + if isinstance(data, list) and data: + stdout.write('[0] -> ') + + for i, each in enumerate(data): + if i > 0: + stdout.write( + f'{" " * indent_level}{obj.__class__.__name__}.{name}[{i}] -> ' + ) + + print(repr(each)) + _test_attributes(each, indent_level + INDENTATION) + + continue + + print(f' -> {data!r}') + + if is_local(data): + _test_attributes(data, indent_level + INDENTATION) + + +def test_attributes(obj: object) -> None: + print(f'{obj!r} -> ') + _test_attributes(obj, INDENTATION) + + +async def run() -> None: + async with topgg.DBLClient(os.getenv('TOPGG_TOKEN')) as tg: + bot = await tg.get_bot_info(432610292342587392) + + test_attributes(bot) + + await asyncio.sleep(1) + bots = await tg.get_bots(limit=250, offset=50, sort=topgg.SortBy.MONTHLY_VOTES) + + for b in bots: + test_attributes(b) + + await asyncio.sleep(1) + await tg.post_guild_count(topgg.StatsWrapper(2)) + + await asyncio.sleep(1) + posted_stats = await tg.get_guild_count() + + assert posted_stats.server_count == 2 + test_attributes(posted_stats) + + await asyncio.sleep(1) + voters = await tg.get_bot_votes() + + for voter in voters: + test_attributes(voter) + + await asyncio.sleep(1) + is_weekend = await tg.get_weekend_status() + + assert isinstance(is_weekend, bool) + + await asyncio.sleep(1) + has_voted = await tg.get_user_vote(661200758510977084) + + assert isinstance(has_voted, bool) + + +if __name__ == '__main__': + asyncio.run(run()) diff --git a/test_autoposter.py b/test_autoposter.py new file mode 100644 index 00000000..fceaa9a2 --- /dev/null +++ b/test_autoposter.py @@ -0,0 +1,51 @@ +ORIGINAL_INTERVAL_INSTRUCTION = 'await asyncio.sleep(self.interval)' +MODIFIED_INTERVAL_INSTRUCTION = 'await asyncio.sleep(2)' + + +def replace_autopost_file(former: str, latter: str) -> None: + autopost_file_contents = None + + with open('./topgg/autopost.py', 'r') as autopost_file: + autopost_file_contents = autopost_file.read().replace(former, latter) + + with open('./topgg/autopost.py', 'w') as autopost_file: + autopost_file.write(autopost_file_contents) + + +replace_autopost_file(ORIGINAL_INTERVAL_INSTRUCTION, MODIFIED_INTERVAL_INSTRUCTION) + + +import topgg + +import asyncio +import os + + +async def run() -> None: + try: + async with topgg.DBLClient(os.getenv('TOPGG_TOKEN')) as tg: + autoposter = tg.autopost() + + @autoposter.stats + def get_guild_count() -> int: + return topgg.StatsWrapper(2) + + @autoposter.on_success + def success() -> None: + print('Successfully posted statistics to the Top.gg API!') + + @autoposter.on_error + def error(exc: Exception) -> None: + print(f'Error: {exc!r}') + + autoposter.start() + + await asyncio.sleep(15) + finally: + replace_autopost_file( + MODIFIED_INTERVAL_INSTRUCTION, ORIGINAL_INTERVAL_INSTRUCTION + ) + + +if __name__ == '__main__': + asyncio.run(run()) diff --git a/tests/test_autopost.py b/tests/test_autopost.py deleted file mode 100644 index b83a0cc0..00000000 --- a/tests/test_autopost.py +++ /dev/null @@ -1,95 +0,0 @@ -import datetime - -import mock -import pytest -from aiohttp import ClientSession -from pytest_mock import MockerFixture - -from topgg import DBLClient -from topgg.autopost import AutoPoster -from topgg.errors import HTTPException, TopGGException - - -MOCK_TOKEN = ".eyJfdCI6IiIsImlkIjoiMzY0ODA2MDI5ODc2NTU1Nzc2In0=." - - -@pytest.fixture -def session() -> ClientSession: - return mock.Mock(ClientSession) - - -@pytest.fixture -def autopost(session: ClientSession) -> AutoPoster: - return AutoPoster(DBLClient(MOCK_TOKEN, session=session)) - - -@pytest.mark.asyncio -async def test_AutoPoster_breaks_autopost_loop_on_401( - mocker: MockerFixture, session: ClientSession -) -> None: - response = mock.Mock("reason, status") - response.reason = "Unauthorized" - response.status = 401 - - mocker.patch( - "topgg.DBLClient.post_guild_count", side_effect=HTTPException(response, {}) - ) - - callback = mock.Mock() - autopost = DBLClient(MOCK_TOKEN, session=session).autopost().stats(callback) - assert isinstance(autopost, AutoPoster) - assert not isinstance(autopost.stats()(callback), AutoPoster) - - with pytest.raises(HTTPException): - await autopost.start() - - callback.assert_called_once() - assert not autopost.is_running - - -@pytest.mark.asyncio -async def test_AutoPoster_raises_missing_stats(autopost: AutoPoster) -> None: - with pytest.raises( - TopGGException, match="you must provide a callback that returns the stats." - ): - await autopost.start() - - -@pytest.mark.asyncio -async def test_AutoPoster_raises_already_running(autopost: AutoPoster) -> None: - autopost.stats(mock.Mock()).start() - with pytest.raises(TopGGException, match="the autopost is already running."): - await autopost.start() - - -@pytest.mark.asyncio -async def test_AutoPoster_interval_too_short(autopost: AutoPoster) -> None: - with pytest.raises(ValueError, match="interval must be greated than 900 seconds."): - autopost.set_interval(50) - - -@pytest.mark.asyncio -async def test_AutoPoster_error_callback( - mocker: MockerFixture, autopost: AutoPoster -) -> None: - error_callback = mock.Mock() - response = mock.Mock("reason, status") - response.reason = "Internal Server Error" - response.status = 500 - side_effect = HTTPException(response, {}) - - mocker.patch("topgg.DBLClient.post_guild_count", side_effect=side_effect) - task = autopost.on_error(error_callback).stats(mock.Mock()).start() - autopost.stop() - await task - error_callback.assert_called_once_with(side_effect) - - -def test_AutoPoster_interval(autopost: AutoPoster): - assert autopost.interval == 900 - autopost.set_interval(datetime.timedelta(hours=1)) - assert autopost.interval == 3600 - autopost.interval = datetime.timedelta(hours=2) - assert autopost.interval == 7200 - autopost.interval = 3600 - assert autopost.interval == 3600 diff --git a/tests/test_client.py b/tests/test_client.py deleted file mode 100644 index f0a9c456..00000000 --- a/tests/test_client.py +++ /dev/null @@ -1,54 +0,0 @@ -import mock -import pytest - -import topgg - - -MOCK_TOKEN = ".eyJfdCI6IiIsImlkIjoiMzY0ODA2MDI5ODc2NTU1Nzc2In0=." - - -@pytest.mark.asyncio -async def test_DBLClient_post_guild_count_with_no_args(): - client = topgg.DBLClient(MOCK_TOKEN) - with pytest.raises(TypeError, match="stats or guild_count must be provided."): - await client.post_guild_count() - - -@pytest.mark.asyncio -async def test_DBLClient_get_weekend_status(monkeypatch): - client = topgg.DBLClient(MOCK_TOKEN) - monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock()) - await client.get_weekend_status() - client._DBLClient__request.assert_called_once() - - -@pytest.mark.asyncio -async def test_DBLClient_post_guild_count(monkeypatch): - client = topgg.DBLClient(MOCK_TOKEN) - monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock()) - await client.post_guild_count(guild_count=123) - client._DBLClient__request.assert_called_once() - - -@pytest.mark.asyncio -async def test_DBLClient_get_guild_count(monkeypatch): - client = topgg.DBLClient(MOCK_TOKEN) - monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value={})) - await client.get_guild_count() - client._DBLClient__request.assert_called_once() - - -@pytest.mark.asyncio -async def test_DBLClient_get_bot_votes(monkeypatch): - client = topgg.DBLClient(MOCK_TOKEN) - monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value=[])) - await client.get_bot_votes() - client._DBLClient__request.assert_called_once() - - -@pytest.mark.asyncio -async def test_DBLClient_get_user_vote(monkeypatch): - client = topgg.DBLClient(MOCK_TOKEN) - monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value={"voted": 1})) - await client.get_user_vote(1234) - client._DBLClient__request.assert_called_once() diff --git a/tests/test_data_container.py b/tests/test_data_container.py deleted file mode 100644 index 978574fb..00000000 --- a/tests/test_data_container.py +++ /dev/null @@ -1,60 +0,0 @@ -import pytest - -from topgg.data import DataContainerMixin, data -from topgg.errors import TopGGException - - -@pytest.fixture -def data_container() -> DataContainerMixin: - dc = DataContainerMixin() - dc.set_data("TEXT") - dc.set_data(200) - dc.set_data({"a": "b"}) - return dc - - -async def _async_callback( - text: str = data(str), number: int = data(int), mapping: dict = data(dict) -): - ... - - -def _sync_callback( - text: str = data(str), number: int = data(int), mapping: dict = data(dict) -): - ... - - -def _invalid_callback(number: float = data(float)): - ... - - -@pytest.mark.asyncio -async def test_data_container_invoke_async_callback(data_container: DataContainerMixin): - await data_container._invoke_callback(_async_callback) - - -@pytest.mark.asyncio -async def test_data_container_invoke_sync_callback(data_container: DataContainerMixin): - await data_container._invoke_callback(_sync_callback) - - -def test_data_container_raises_data_already_exists(data_container: DataContainerMixin): - with pytest.raises( - TopGGException, - match=" already exists. If you wish to override it, " - "pass True into the override parameter.", - ): - data_container.set_data("TEST") - - -@pytest.mark.asyncio -async def test_data_container_raises_key_error(data_container: DataContainerMixin): - with pytest.raises(KeyError): - await data_container._invoke_callback(_invalid_callback) - - -def test_data_container_get_data(data_container: DataContainerMixin): - assert data_container.get_data(str) == "TEXT" - assert data_container.get_data(float) is None - assert isinstance(data_container.get_data(set, set()), set) diff --git a/tests/test_ratelimiter.py b/tests/test_ratelimiter.py deleted file mode 100644 index 998a7357..00000000 --- a/tests/test_ratelimiter.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -from topgg.ratelimiter import Ratelimiter - -n = period = 10 - - -@pytest.fixture -def limiter() -> Ratelimiter: - return Ratelimiter(max_calls=n, period=period) - - -@pytest.mark.asyncio -async def test_AsyncRateLimiter_calls(limiter: Ratelimiter) -> None: - for _ in range(n): - async with limiter: - pass - - assert len(limiter._Ratelimiter__calls) == limiter._Ratelimiter__max_calls == n - - -@pytest.mark.asyncio -async def test_AsyncRateLimiter_timespan_property(limiter: Ratelimiter) -> None: - for _ in range(n): - async with limiter: - pass - - assert limiter._timespan < period diff --git a/tests/test_type.py b/tests/test_type.py deleted file mode 100644 index caec363c..00000000 --- a/tests/test_type.py +++ /dev/null @@ -1,202 +0,0 @@ -import pytest - -from topgg import types - -d: dict = { - "defAvatar": "6debd47ed13483642cf09e832ed0bc1b", - "invite": "", - "website": "https://top.gg", - "support": "KYZsaFb", - "github": "https://github.com/top-gg/Luca", - "longdesc": "Luca only works in the **Discord Bot List** server. \nPrepend commands with the prefix `-` or " - "`@Luca#1375`. \n**Please refrain from using these commands in non testing channels.**\n- `botinfo " - "@bot` Shows bot info, title redirects to site listing.\n- `bots @user`* Shows all bots of that user, " - "includes bots in the queue.\n- `owner / -owners @bot`* Shows all owners of that bot.\n- `prefix " - "@bot`* Shows the prefix of that bot.\n* Mobile friendly version exists. Just add `noembed` to the " - "end of the command.\n", - "shortdesc": "Luca is a bot for managing and informing members of the server", - "prefix": "- or @Luca#1375", - "lib": None, - "clientid": "264811613708746752", - "avatar": "7edcc4c6fbb0b23762455ca139f0e1c9", - "id": "264811613708746752", - "discriminator": "1375", - "username": "Luca", - "date": "2017-04-26T18:08:17.125Z", - "server_count": 2, - "guilds": ["417723229721853963", "264445053596991498"], - "shards": [], - "monthlyPoints": 19, - "points": 397, - "certifiedBot": False, - "owners": ["129908908096487424"], - "tags": ["Moderation", "Role Management", "Logging"], - "donatebotguildid": "", -} - -query_dict = {"qwe": "1", "rty": "2", "uio": "3"} - -vote_data_dict = { - "type": "test", - "query": "?" + "&".join(f"{k}={v}" for k, v in query_dict.items()), - "user": "1", -} - -bot_vote_dict = { - "bot": "2", - "user": "3", - "type": "test", - "query": "?" + "&".join(f"{k}={v}" for k, v in query_dict.items()), -} - -server_vote_dict = { - "guild": "4", - "user": "5", - "type": "upvote", - "query": "?" + "&".join(f"{k}={v}" for k, v in query_dict.items()), -} - -user_data_dict = { - "discriminator": "0001", - "avatar": "a_1241439d430def25c100dd28add2d42f", - "id": "140862798832861184", - "username": "Xetera", - "defAvatar": "322c936a8c8be1b803cd94861bdfa868", - "admin": True, - "webMod": True, - "mod": True, - "certifiedDev": False, - "supporter": False, - "social": {}, -} - -bot_stats_dict = {"shards": [1, 5, 8]} - - -@pytest.fixture -def data_dict() -> types.DataDict: - return types.DataDict(**d) - - -@pytest.fixture -def bot_data() -> types.BotData: - return types.BotData(**d) - - -@pytest.fixture -def user_data() -> types.UserData: - return types.UserData(**user_data_dict) - - -@pytest.fixture -def widget_options() -> types.WidgetOptions: - return types.WidgetOptions(id=int(d["id"])) - - -@pytest.fixture -def vote_data() -> types.VoteDataDict: - return types.VoteDataDict(**vote_data_dict) - - -@pytest.fixture -def bot_vote_data() -> types.BotVoteData: - return types.BotVoteData(**bot_vote_dict) - - -@pytest.fixture -def server_vote_data() -> types.GuildVoteData: - return types.GuildVoteData(**server_vote_dict) - - -@pytest.fixture -def bot_stats_data() -> types.BotStatsData: - return types.BotStatsData(**bot_stats_dict) - - -def test_data_dict_fields(data_dict: types.DataDict) -> None: - for attr in data_dict: - if "id" in attr.lower(): - assert isinstance(data_dict[attr], int) or data_dict[attr] is None - assert data_dict.get(attr) == data_dict[attr] == getattr(data_dict, attr) - - -def test_bot_data_fields(bot_data: types.BotData) -> None: - bot_data.github = "I'm a GitHub link!" - bot_data.support = "Support has arrived!" - - for attr in bot_data: - if "id" in attr.lower(): - assert isinstance(bot_data[attr], int) or bot_data[attr] is None - elif attr in ("owners", "guilds"): - for item in bot_data[attr]: - assert isinstance(item, int) - assert bot_data.get(attr) == bot_data[attr] == getattr(bot_data, attr) - - -def test_widget_options_fields(widget_options: types.WidgetOptions) -> None: - assert widget_options["colors"] == widget_options["colours"] - - widget_options.colours = {"background": 0} - widget_options["colours"]["text"] = 255 - assert widget_options.colours == widget_options["colors"] - - for attr in widget_options: - if "id" in attr.lower(): - assert isinstance(widget_options[attr], int) or widget_options[attr] is None - assert ( - widget_options.get(attr) - == widget_options[attr] - == widget_options[attr] - == getattr(widget_options, attr) - ) - - -def test_vote_data_fields(vote_data: types.VoteDataDict) -> None: - assert isinstance(vote_data.query, dict) - vote_data.type = "upvote" - - for attr in vote_data: - assert getattr(vote_data, attr) == vote_data.get(attr) == vote_data[attr] - - -def test_bot_vote_data_fields(bot_vote_data: types.BotVoteData) -> None: - assert isinstance(bot_vote_data.query, dict) - bot_vote_data.type = "upvote" - - assert isinstance(bot_vote_data["bot"], int) - for attr in bot_vote_data: - assert ( - getattr(bot_vote_data, attr) - == bot_vote_data.get(attr) - == bot_vote_data[attr] - ) - - -def test_server_vote_data_fields(server_vote_data: types.BotVoteData) -> None: - assert isinstance(server_vote_data.query, dict) - server_vote_data.type = "upvote" - - assert isinstance(server_vote_data["guild"], int) - for attr in server_vote_data: - assert ( - getattr(server_vote_data, attr) - == server_vote_data.get(attr) - == server_vote_data[attr] - ) - - -def test_bot_stats_data_attrs(bot_stats_data: types.BotStatsData) -> None: - for count in ("server_count", "shard_count"): - assert isinstance(bot_stats_data[count], int) or bot_stats_data[count] is None - assert isinstance(bot_stats_data.shards, list) - if bot_stats_data.shards: - for shard in bot_stats_data.shards: - assert isinstance(shard, int) - - -def test_user_data_attrs(user_data: types.UserData) -> None: - assert isinstance(user_data.social, types.SocialData) - for attr in user_data: - if "id" in attr.lower(): - assert isinstance(user_data[attr], int) or user_data[attr] is None - assert user_data[attr] == getattr(user_data, attr) == user_data.get(attr) diff --git a/tests/test_webhook.py b/tests/test_webhook.py deleted file mode 100644 index 8ef3c71d..00000000 --- a/tests/test_webhook.py +++ /dev/null @@ -1,80 +0,0 @@ -import typing as t - -import aiohttp -import mock -import pytest - -from topgg import WebhookManager, WebhookType -from topgg.errors import TopGGException - -auth = "youshallnotpass" - - -@pytest.fixture -def webhook_manager() -> WebhookManager: - return ( - WebhookManager() - .endpoint() - .type(WebhookType.BOT) - .auth(auth) - .route("/dbl") - .callback(print) - .add_to_manager() - .endpoint() - .type(WebhookType.GUILD) - .auth(auth) - .route("/dsl") - .callback(print) - .add_to_manager() - ) - - -def test_WebhookManager_routes(webhook_manager: WebhookManager) -> None: - assert len(webhook_manager.app.router.routes()) == 2 - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "headers, result, state", - [({"authorization": auth}, 200, True), ({}, 401, False)], -) -async def test_WebhookManager_validates_auth( - webhook_manager: WebhookManager, headers: t.Dict[str, str], result: int, state: bool -) -> None: - await webhook_manager.start(5000) - - try: - for path in ("dbl", "dsl"): - async with aiohttp.request( - "POST", f"http://localhost:5000/{path}", headers=headers, json={} - ) as r: - assert r.status == result - finally: - await webhook_manager.close() - assert not webhook_manager.is_running - - -def test_WebhookEndpoint_callback_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing callback.", - ): - webhook_manager.endpoint().add_to_manager() - - -def test_WebhookEndpoint_route_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing type.", - ): - webhook_manager.endpoint().callback(mock.Mock()).add_to_manager() - - -def test_WebhookEndpoint_type_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing route.", - ): - webhook_manager.endpoint().callback(mock.Mock()).type( - WebhookType.BOT - ).add_to_manager()