Skip to content

Commit 208f6df

Browse files
committed
feat: apply changes related to tests from Top-gg-Community#87
1 parent eaab5f1 commit 208f6df

File tree

10 files changed

+297
-328
lines changed

10 files changed

+297
-328
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
- uses: actions/checkout@v5
1010
- uses: actions/setup-python@v6
1111
with:
12-
python-version: 3.13
12+
python-version: 3.14
1313
- name: Install dependencies
1414
run: python3 -m pip install build twine
1515
- name: Build and publish

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]
12+
python-version: [ '3.10', 3.11, 3.12, 3.13, 3.14 ]
1313
steps:
1414
- uses: actions/checkout@v5
1515
- name: Set up Python ${{ matrix.python-version }}
1616
uses: actions/setup-python@v6
1717
with:
1818
python-version: ${{ matrix.python-version }}
1919
- name: Install
20-
run: python -m pip install .[dev]
20+
run: python -m pip install . pytest mock pytest-mock pytest-asyncio
2121
- name: Test with pytest
2222
run: pytest

MANIFEST.in

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
include LICENSE
2-
include requirements.txt
3-
include README.rst
1+
prune .github
2+
prune .ruff_cache
3+
prune docs
4+
prune examples
5+
prune tests
6+
exclude .gitignore
7+
exclude .readthedocs.yml
8+
exclude ruff.toml
9+
exclude LICENSE
10+
exclude ISSUE_TEMPLATE.md
11+
exclude PULL_REQUEST_TEMPLATE.md

tests/test_autopost.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import datetime
2+
3+
import mock
4+
import pytest
5+
from aiohttp import ClientSession
6+
from pytest_mock import MockerFixture
7+
8+
from topgg import DBLClient
9+
from topgg.autopost import AutoPoster
10+
from topgg.errors import HTTPException, TopGGException
11+
12+
13+
MOCK_TOKEN = ".eyJfdCI6IiIsImlkIjoiMzY0ODA2MDI5ODc2NTU1Nzc2In0=."
14+
15+
16+
@pytest.fixture
17+
def session() -> ClientSession:
18+
return mock.Mock(ClientSession)
19+
20+
21+
@pytest.fixture
22+
def autopost(session: ClientSession) -> AutoPoster:
23+
return AutoPoster(DBLClient(MOCK_TOKEN, session=session))
24+
25+
26+
@pytest.mark.asyncio
27+
async def test_AutoPoster_breaks_autopost_loop_on_401(
28+
mocker: MockerFixture, session: ClientSession
29+
) -> None:
30+
mocker.patch(
31+
"topgg.DBLClient.post_guild_count",
32+
side_effect=HTTPException("Unauthorized", 401),
33+
)
34+
35+
callback = mock.Mock()
36+
autopost = DBLClient(MOCK_TOKEN, session=session).autopost().stats(callback)
37+
38+
assert isinstance(autopost, AutoPoster)
39+
assert not isinstance(autopost.stats()(callback), AutoPoster)
40+
41+
autopost._interval = 1
42+
43+
with pytest.raises(HTTPException):
44+
await autopost.start()
45+
46+
callback.assert_called_once()
47+
assert not autopost.is_running
48+
49+
50+
@pytest.mark.asyncio
51+
async def test_AutoPoster_raises_missing_stats(autopost: AutoPoster) -> None:
52+
with pytest.raises(
53+
TopGGException, match="You must provide a callback that returns the stats."
54+
):
55+
await autopost.start()
56+
57+
58+
@pytest.mark.asyncio
59+
async def test_AutoPoster_raises_already_running(autopost: AutoPoster) -> None:
60+
autopost.stats(mock.Mock()).start()
61+
with pytest.raises(TopGGException, match="The autoposter is already running."):
62+
await autopost.start()
63+
64+
65+
@pytest.mark.asyncio
66+
async def test_AutoPoster_interval_too_short(autopost: AutoPoster) -> None:
67+
with pytest.raises(ValueError, match="interval must be greater than 900 seconds."):
68+
autopost.set_interval(50)
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_AutoPoster_error_callback(
73+
mocker: MockerFixture, autopost: AutoPoster
74+
) -> None:
75+
error_callback = mock.Mock()
76+
side_effect = HTTPException("Internal Server Error", 500)
77+
78+
mocker.patch("topgg.DBLClient.post_guild_count", side_effect=side_effect)
79+
task = autopost.on_error(error_callback).stats(mock.Mock()).start()
80+
autopost.stop()
81+
await task
82+
error_callback.assert_called_once_with(side_effect)
83+
84+
85+
def test_AutoPoster_interval(autopost: AutoPoster):
86+
assert autopost.interval == 900
87+
autopost.set_interval(datetime.timedelta(hours=1))
88+
assert autopost.interval == 3600
89+
autopost.interval = datetime.timedelta(hours=2)
90+
assert autopost.interval == 7200
91+
autopost.interval = 3600
92+
assert autopost.interval == 3600

tests/test_client.py

Lines changed: 34 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
import asyncio
2-
from typing import Optional
3-
41
import mock
52
import pytest
63
from aiohttp import ClientSession
7-
from discord import Client
8-
from discord.ext.commands import Bot
9-
from pytest import CaptureFixture
10-
from pytest_mock import MockerFixture
114

12-
from topgg import DBLClient
13-
from topgg.errors import ClientException, Unauthorized, UnauthorizedDetected
5+
import topgg
146

157

16-
@pytest.fixture
17-
def bot() -> Client:
18-
bot = mock.Mock(Client)
19-
bot.loop = asyncio.get_event_loop()
20-
bot.guilds = []
21-
bot.is_closed.return_value = False
22-
return bot
8+
MOCK_TOKEN = ".eyJfdCI6IiIsImlkIjoiMzY0ODA2MDI5ODc2NTU1Nzc2In0=."
239

2410

2511
@pytest.fixture
@@ -28,162 +14,52 @@ def session() -> ClientSession:
2814

2915

3016
@pytest.fixture
31-
def exc() -> Exception:
32-
return Exception("Test Exception")
33-
34-
35-
@pytest.mark.parametrize(
36-
"autopost, post_shard_count, autopost_interval",
37-
[
38-
(True, True, 900),
39-
(True, False, 900),
40-
(True, True, None),
41-
(True, False, None),
42-
(False, False, None),
43-
(False, False, 0),
44-
],
45-
)
46-
@pytest.mark.asyncio
47-
async def test_DBLClient_validates_constructor_and_passes_for_valid_values(
48-
bot: Client,
49-
mocker: MockerFixture,
50-
autopost: bool,
51-
post_shard_count: bool,
52-
autopost_interval: Optional[int],
53-
session: ClientSession,
54-
) -> None:
55-
mocker.patch("topgg.DBLClient._auto_post", new_callable=mock.AsyncMock) # type: ignore
56-
DBLClient(
57-
bot,
58-
"",
59-
session=session,
60-
autopost=autopost,
61-
post_shard_count=post_shard_count,
62-
autopost_interval=autopost_interval,
63-
)
64-
65-
66-
@pytest.mark.parametrize(
67-
"autopost, post_shard_count, autopost_interval",
68-
[
69-
(True, True, 0),
70-
(True, False, 500),
71-
(False, True, 0),
72-
(False, True, 900),
73-
(False, True, None),
74-
(False, False, 1800),
75-
],
76-
)
77-
def test_DBLClient_validates_constructor_and_fails_for_invalid_values(
78-
bot: Client,
79-
mocker: MockerFixture,
80-
autopost: bool,
81-
post_shard_count: bool,
82-
autopost_interval: Optional[int],
83-
session: ClientSession,
84-
) -> None:
85-
with pytest.raises(ClientException):
86-
DBLClient(
87-
bot,
88-
"",
89-
session=session,
90-
autopost=autopost,
91-
post_shard_count=post_shard_count,
92-
autopost_interval=autopost_interval,
93-
)
17+
def client(session: ClientSession) -> topgg.DBLClient:
18+
return topgg.DBLClient(MOCK_TOKEN, session=session)
9419

9520

9621
@pytest.mark.asyncio
97-
async def test_DBLClient_breaks_autopost_loop_on_401(
98-
bot: Client, mocker: MockerFixture, session: ClientSession
99-
) -> None:
100-
response = mock.Mock("reason, status")
101-
response.reason = "Unauthorized"
102-
response.status = 401
103-
104-
mocker.patch(
105-
"topgg.DBLClient.post_guild_count", side_effect=Unauthorized(response, {})
106-
)
107-
mocker.patch(
108-
"topgg.DBLClient._ensure_bot_user",
109-
new_callable=mock.AsyncMock, # type: ignore
110-
)
111-
112-
obj = DBLClient(bot, "", False, session=session)
113-
114-
with pytest.raises(Unauthorized):
115-
await obj._auto_post()
22+
async def test_DBLClient_post_guild_count_with_no_args(client: topgg.DBLClient):
23+
with pytest.raises(ValueError, match="Got an invalid server count. Got None."):
24+
await client.post_guild_count()
11625

11726

11827
@pytest.mark.asyncio
119-
@pytest.mark.parametrize(
120-
"token",
121-
[None, ""],
122-
# treat None as str to suppress mypy
123-
)
124-
async def test_HTTPClient_fails_for_no_token(
125-
bot: Client, token: str, session: ClientSession
126-
) -> None:
127-
with pytest.raises(UnauthorizedDetected):
128-
await DBLClient(bot=bot, token=token, session=session).post_guild_count()
28+
async def test_DBLClient_get_weekend_status(monkeypatch, client: topgg.DBLClient):
29+
monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock())
30+
await client.get_weekend_status()
31+
client._DBLClient__request.assert_called_once()
12932

13033

13134
@pytest.mark.asyncio
132-
async def test_Client_with_default_autopost_error_handler(
133-
mocker: MockerFixture,
134-
capsys: CaptureFixture[str],
135-
session: ClientSession,
136-
exc: Exception,
137-
) -> None:
138-
client = Client()
139-
mocker.patch("topgg.DBLClient._auto_post", new_callable=mock.AsyncMock) # type: ignore
140-
dbl = DBLClient(client, "", True, session=session)
141-
assert client.on_autopost_error == dbl.on_autopost_error
142-
await client.on_autopost_error(exc)
143-
assert "Ignoring exception in auto post loop" in capsys.readouterr().err
35+
async def test_DBLClient_post_guild_count(monkeypatch, client: topgg.DBLClient):
36+
monkeypatch.setattr("topgg.DBLClient._DBLClient__request", mock.AsyncMock())
37+
await client.post_guild_count(guild_count=123)
38+
client._DBLClient__request.assert_called_once()
14439

14540

14641
@pytest.mark.asyncio
147-
async def test_Client_with_custom_autopost_error_handler(
148-
mocker: MockerFixture, session: ClientSession, exc: Exception
149-
) -> None:
150-
client = Client()
151-
state = False
42+
async def test_DBLClient_get_guild_count(monkeypatch, client: topgg.DBLClient):
43+
monkeypatch.setattr(
44+
"topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value={})
45+
)
46+
await client.get_guild_count()
47+
client._DBLClient__request.assert_called_once()
15248

153-
@client.event
154-
async def on_autopost_error(exc: Exception) -> None:
155-
nonlocal state
156-
state = True
15749

158-
mocker.patch("topgg.DBLClient._auto_post", new_callable=mock.AsyncMock) # type: ignore
159-
DBLClient(client, "", True, session=session)
160-
await client.on_autopost_error(exc)
161-
assert state
50+
@pytest.mark.asyncio
51+
async def test_DBLClient_get_bot_votes(monkeypatch, client: topgg.DBLClient):
52+
monkeypatch.setattr(
53+
"topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value=[])
54+
)
55+
await client.get_bot_votes()
56+
client._DBLClient__request.assert_called_once()
16257

16358

16459
@pytest.mark.asyncio
165-
async def test_Bot_with_autopost_error_listener(
166-
mocker: MockerFixture,
167-
capsys: CaptureFixture[str],
168-
session: ClientSession,
169-
exc: Exception,
170-
) -> None:
171-
bot = Bot("")
172-
state = False
173-
174-
@bot.listen()
175-
async def on_autopost_error(exc: Exception) -> None:
176-
nonlocal state
177-
state = True
178-
179-
mocker.patch("topgg.DBLClient._auto_post", new_callable=mock.AsyncMock) # type: ignore
180-
DBLClient(bot, "", True, session=session)
181-
182-
# await to make sure all the listeners were run before asserting
183-
# as <Bot>.dispatch schedules the events and will continue
184-
# to the assert line without finishing the event callbacks
185-
await bot.on_autopost_error(exc)
186-
await on_autopost_error(exc)
187-
188-
assert "Ignoring exception in auto post loop" not in capsys.readouterr().err
189-
assert state
60+
async def test_DBLClient_get_user_vote(monkeypatch, client: topgg.DBLClient):
61+
monkeypatch.setattr(
62+
"topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value={"voted": 1})
63+
)
64+
await client.get_user_vote(1234)
65+
client._DBLClient__request.assert_called_once()

0 commit comments

Comments
 (0)