Skip to content

test: minor improvements to test #162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
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
14 changes: 7 additions & 7 deletions nextcore/http/client/wrappers/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ async def modify_group_dm(

async def modify_guild_channel(
self,
authentication: BearerAuthentication,
authentication: BotAuthentication,
channel_id: Snowflake,
*,
name: str | UndefinedType = UNDEFINED,
Expand Down Expand Up @@ -580,7 +580,7 @@ async def get_channel_messages(
authentication: BotAuthentication,
channel_id: Snowflake,
*,
around: int,
around: Snowflake,
limit: int | UndefinedType,
bucket_priority: int = 0,
global_priority: int = 0,
Expand All @@ -594,7 +594,7 @@ async def get_channel_messages(
authentication: BotAuthentication,
channel_id: Snowflake,
*,
before: int,
before: Snowflake,
limit: int | UndefinedType,
bucket_priority: int = 0,
global_priority: int = 0,
Expand All @@ -608,7 +608,7 @@ async def get_channel_messages(
authentication: BotAuthentication,
channel_id: Snowflake,
*,
after: int,
after: Snowflake,
limit: int | UndefinedType,
bucket_priority: int = 0,
global_priority: int = 0,
Expand All @@ -633,9 +633,9 @@ async def get_channel_messages(
authentication: BotAuthentication,
channel_id: Snowflake,
*,
around: int | UndefinedType = UNDEFINED,
before: int | UndefinedType = UNDEFINED,
after: int | UndefinedType = UNDEFINED,
around: Snowflake | UndefinedType = UNDEFINED,
before: Snowflake | UndefinedType = UNDEFINED,
after: Snowflake | UndefinedType = UNDEFINED,
limit: int | UndefinedType = UNDEFINED,
bucket_priority: int = 0,
global_priority: int = 0,
Expand Down
175 changes: 175 additions & 0 deletions nextcore/http/client/wrappers/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
RoleData,
RolePositionData,
Snowflake,
VideoQualityModes,
VoiceRegionData,
WelcomeChannelData,
WelcomeScreenData,
Expand Down Expand Up @@ -404,6 +405,180 @@ async def get_guild_channels(
# TODO: Make this verify the data from Discord
return await r.json() # type: ignore [no-any-return]

async def create_guild_channel(
self,
authentication: BotAuthentication,
guild_id: Snowflake,
name: str,
*,
type: int | None | UndefinedType,
topic: str | None | UndefinedType = UNDEFINED,
bitrate: int | None | UndefinedType = UNDEFINED,
user_limit: int | None | UndefinedType = UNDEFINED,
rate_limit_per_user: int | None | UndefinedType = UNDEFINED,
position: int | None | UndefinedType = UNDEFINED,
permission_overwrites: list[dict[str, Any]] | None | UndefinedType = UNDEFINED,
parent_id: Snowflake | None | UndefinedType = UNDEFINED,
nsfw: bool | None | UndefinedType = UNDEFINED,
rtc_region: str | None | UndefinedType = UNDEFINED,
video_quality_mode: VideoQualityModes | None | UndefinedType = UNDEFINED,
default_auto_archive_duration: int | None | UndefinedType = UNDEFINED,
default_reaction_emoji: Any | None | UndefinedType = UNDEFINED,
available_tags: list[Any] | None | UndefinedType = UNDEFINED,
default_sort_order: int | None | UndefinedType = UNDEFINED,
reason: str | UndefinedType = UNDEFINED,
bucket_priority: int = 0,
global_priority: int = 0,
wait: bool = True,
) -> ChannelData:
"""Creates a channel

Read the `documentation <https://discord.dev/resources/guild#create-guild-channel>`__

Parameters
----------
authentication:
The auth info.
guild_id:
The guild to create a channel in.
name:
The name of the channel.

.. note::
This has to be between 1-100 characters.
type:
The type of the channel.
topic:
The channel topic or forum guidelines if creating a forum channel.

.. note::
This has to be between 0-1024 characters
bitrate:
The voice bitrate.

.. note::
This has to be more than 8000.

- If the guild is boost level 3 or it has the ``VIP_REGIONS`` feature, the max is 384000
- If the guild is boost level 2 this is 256000
- If the guild is boost level 1 this is 128000
- Else this is 96000
user_limit:
The most amount of people that can be in a voice channel at once

.. note::
Stage channels are not affected by this
rate_limit_per_user:
amount of seconds a user has to wait before sending another message or create another thread.

.. note::
This has to be between 0-21600
.. note::
Bots and members with ``MANAGE_MESSAGES`` or ``MANAGE_CHANNEL`` are immune.
position:
The sorting position of the channel inside its category.
permission_overwrites:
The channels permissions overwrites.

.. note::
The allow or deny keys default to 0.
.. note::
Only permissions your bot has can be allowed/denied.

``MANAGE_ROLES`` can also only be allowed/denied by members with the ``ADMINISTRATOR`` permission
parent_id:
The category to put this channel under.
nsfw:
If the channel is age restricted.
rtc_region:
The voice region id to use. If this is :data:`None` this will automatically decide a voice region when needed.
video_quality_mode:
The quality mode for camera.

Only affects voice channels and stage channels.
default_auto_archive_duration:
The default auto archive duration used by the Discord client in minutes.
default_reaction_emoji:
The default reaction emoji that will be shown in forum channels on posts
available_tags:
The tags that can be added to forum posts.
default_sort_order:
The default sort order for the forum posts.
reason:
The reason to put in the audit log
global_priority:
The priority of the request for the global rate-limiter.
bucket_priority:
The priority of the request for the bucket rate-limiter.
wait:
Wait when rate limited.

This will raise :exc:`RateLimitedError` if set to :data:`False` and you are rate limited.

Raises
------
RateLimitedError
You are rate limited, and ``wait`` was set to :data:`False`
"""
route = Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id)

headers = {}

# These have different behaviour when not provided and set to None.
# This only adds them if they are provided (not Undefined)
if reason is not UNDEFINED:
headers["X-Audit-Log-Reason"] = reason

payload: dict[str, Any] = {"name": name}

# These have different behaviour when not provided and set to None.
# This only adds them if they are provided (not Undefined)
if type is not UNDEFINED:
payload["type"] = type
if topic is not UNDEFINED:
payload["topic"] = topic
if bitrate is not UNDEFINED:
payload["bitrate"] = bitrate
if user_limit is not UNDEFINED:
payload["user_limit"] = user_limit
if rate_limit_per_user is not UNDEFINED:
payload["rate_limit_per_user"] = rate_limit_per_user
if position is not UNDEFINED:
payload["position"] = position
if permission_overwrites is not UNDEFINED:
payload["permission_overwrites"] = permission_overwrites
if parent_id is not UNDEFINED:
payload["parent_id"] = parent_id
if nsfw is not UNDEFINED:
payload["nsfw"] = nsfw
if rtc_region is not UNDEFINED:
payload["rtc_region"] = rtc_region
if video_quality_mode is not UNDEFINED:
payload["video_quality_mode"] = video_quality_mode
if default_auto_archive_duration is not UNDEFINED:
payload["default_auto_archive_duration"] = default_auto_archive_duration
if default_reaction_emoji is not UNDEFINED:
payload["default_reaction_emoji"] = default_reaction_emoji
if available_tags is not UNDEFINED:
payload["available_tags"] = available_tags
if default_sort_order is not UNDEFINED:
payload["default_sort_order"] = default_sort_order

r = await self._request(
route,
json=payload,
rate_limit_key=authentication.rate_limit_key,
headers={"Authorization": str(authentication)},
bucket_priority=bucket_priority,
global_priority=global_priority,
wait=wait,
)

# TODO: Make this verify the data from Discord
return await r.json() # type: ignore [no-any-return]



# TODO: Implement create guild channel

async def modify_guild_channel_positions(
Expand Down
6 changes: 1 addition & 5 deletions nextcore/http/client/wrappers/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ async def create_webhook(
global_priority=global_priority,
wait=wait,
)

# TODO: Make this verify the payload from discord?
return await r.json() # type: ignore [no-any-return]

Expand Down Expand Up @@ -572,7 +571,7 @@ async def delete_webhook(
if reason is not UNDEFINED:
headers["X-Audit-Log-Reason"] = reason

r = await self._request(
await self._request(
route,
rate_limit_key=authentication.rate_limit_key,
headers=headers,
Expand All @@ -581,9 +580,6 @@ async def delete_webhook(
wait=wait,
)

# TODO: Make this verify the payload from discord?
return await r.json() # type: ignore [no-any-return]

async def delete_webhook_with_token(
self,
webhook_id: Snowflake,
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ typing-extensions = "^4.1.1" # Same as above
orjson = {version = "^3.6.8", optional = true}
types-orjson = {version = "^3.6.2", optional = true}
discord-typings = "^0.5.0"
pytest-harmony = {path = "../pytest-harmony"}

[tool.poetry.dev-dependencies]
Sphinx = "^4.4.0"
Expand Down Expand Up @@ -81,7 +82,7 @@ testpaths = ["tests"]
pythonPlatform = "All"
typeCheckingMode = "strict"
pythonVersion = "3.8"
exclude = ["tests/"]
#exclude = ["tests/"]

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
Empty file added tests/__init__.py
Empty file.
Empty file added tests/common/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions tests/common/test_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,21 @@ def false_callback(event: str | None = None) -> bool:
# Check for logging errors.
error_count = len([record for record in caplog.records if record.levelname == "ERROR"])
assert error_count == 0, "Logged errors where present"

@mark.asyncio
@mark.parametrize("event_name", [None, "test"])
async def test_remove_listener(event_name):
failed: Future[None] = Future()

async def handler():
failed.set_result(None)

dispatcher = Dispatcher()

dispatcher.add_listener(handler, event_name)
dispatcher.remove_listener(handler, event_name)

await dispatcher.dispatch(event_name)

with raises(AsyncioTimeoutError):
await wait_for(dispatcher.wait_for(lambda: True, event_name), timeout=1)
21 changes: 21 additions & 0 deletions tests/common/test_times_per.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from asyncio import Future, create_task, sleep

from pytest import mark, raises

from nextcore.common.errors import RateLimitedError
Expand Down Expand Up @@ -42,6 +44,25 @@ async def test_exception_undos():
pass


@mark.asyncio
@match_time(0.1, 0.01)
async def test_exception_undos_with_pending():
rate_limiter = TimesPer(1, 1)
waiting_future: Future[None] = Future()

async def wait_for_a_second():
async with rate_limiter.acquire():
waiting_future.set_result(None)
await sleep(0.1)
raise

create_task(wait_for_a_second())
await waiting_future

async with rate_limiter.acquire():
...


@mark.asyncio
async def test_no_wait():
rate_limiter = TimesPer(1, 1)
Expand Down
Empty file added tests/gateway/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions tests/gateway/test_decompressor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import zlib

from nextcore.gateway import Decompressor


def test_decompress():
decompressor = Decompressor()

content = b"Hello, world!"
compressed = zlib.compress(content) + Decompressor.ZLIB_SUFFIX

assert decompressor.decompress(compressed) == content
Empty file added tests/http/__init__.py
Empty file.
Empty file.
23 changes: 23 additions & 0 deletions tests/http/global_rate_limiter/test_unlimited.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from asyncio import CancelledError
from asyncio import TimeoutError as AsyncioTimeoutError
from asyncio import sleep, wait_for

Expand Down Expand Up @@ -48,3 +49,25 @@ async def test_no_wait() -> None:
with raises(RateLimitedError):
async with rate_limiter.acquire(wait=False):
...


@mark.asyncio
async def test_cancel() -> None:
rate_limiter = UnlimitedGlobalRateLimiter()

with raises(CancelledError):
async with rate_limiter.acquire():
raise CancelledError()
assert len(rate_limiter._pending_requests) == 0, "Pending request was not cleared" # type: ignore [reportPrivateUsage]

@mark.asyncio
@match_time(1, .1)
async def test_reset() -> None:
rate_limiter = UnlimitedGlobalRateLimiter()

rate_limiter.update(1)

await sleep(.5) # Ensure it registers

async with rate_limiter.acquire():
...
Loading