Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.13
python-version: 3.14
- name: Install dependencies
run: python3 -m pip install build twine
- name: Build and publish
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]
python-version: [ '3.10', 3.11, 3.12, 3.13, 3.14 ]
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: python -m pip install .[dev]
run: python -m pip install . pytest mock pytest-mock pytest-asyncio pytest-cov
- name: Test with pytest
run: pytest
13 changes: 3 additions & 10 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
prune .github
prune .pytest_cache
prune .ruff_cache
prune build
prune docs
prune examples
prune scripts
prune tests

exclude .coverage
exclude .gitignore
exclude .readthedocs.yml
exclude ISSUE_TEMPLATE.md
exclude mypy.ini
exclude PULL_REQUEST_TEMPLATE.md
exclude pytest.ini
exclude ruff.toml
exclude LICENSE
exclude LICENSE
exclude ISSUE_TEMPLATE.md
exclude PULL_REQUEST_TEMPLATE.md
187 changes: 92 additions & 95 deletions tests/test_autopost.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,92 @@
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
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:
mocker.patch(
"topgg.DBLClient.post_guild_count",
side_effect=HTTPException("Unauthorized", 401),
)

callback = mock.Mock()
autopost = DBLClient(MOCK_TOKEN, session=session).autopost().stats(callback)

assert isinstance(autopost, AutoPoster)
assert not isinstance(autopost.stats()(callback), AutoPoster)

autopost._interval = 1

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 autoposter 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 greater 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()
side_effect = HTTPException("Internal Server Error", 500)

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
43 changes: 27 additions & 16 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
import mock
import pytest
from aiohttp import ClientSession

import topgg


MOCK_TOKEN = ".eyJfdCI6IiIsImlkIjoiMzY0ODA2MDI5ODc2NTU1Nzc2In0=."


@pytest.fixture
def session() -> ClientSession:
return mock.Mock(ClientSession)


@pytest.fixture
def client(session: ClientSession) -> topgg.DBLClient:
return topgg.DBLClient(MOCK_TOKEN, session=session)


@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."):
async def test_DBLClient_post_guild_count_with_no_args(client: topgg.DBLClient):
with pytest.raises(ValueError, match="Got an invalid server count. Got None."):
await client.post_guild_count()


@pytest.mark.asyncio
async def test_DBLClient_get_weekend_status(monkeypatch):
client = topgg.DBLClient(MOCK_TOKEN)
async def test_DBLClient_get_weekend_status(monkeypatch, client: topgg.DBLClient):
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)
async def test_DBLClient_post_guild_count(monkeypatch, client: topgg.DBLClient):
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={}))
async def test_DBLClient_get_guild_count(monkeypatch, client: topgg.DBLClient):
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=[]))
async def test_DBLClient_get_bot_votes(monkeypatch, client: topgg.DBLClient):
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}))
async def test_DBLClient_get_user_vote(monkeypatch, client: topgg.DBLClient):
monkeypatch.setattr(
"topgg.DBLClient._DBLClient__request", mock.AsyncMock(return_value={"voted": 1})
)
await client.get_user_vote(1234)
client._DBLClient__request.assert_called_once()
9 changes: 3 additions & 6 deletions tests/test_data_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@ def data_container() -> DataContainerMixin:

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)):
...
def _invalid_callback(number: float = data(float)): ...


@pytest.mark.asyncio
Expand Down
56 changes: 28 additions & 28 deletions tests/test_ratelimiter.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
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
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
Loading
Loading