Repository: hikari-py/hikari
Pull request #628 (2021-05-30 17:51:02)

ComponentNotRunningError error refactors
### Summary
* Rename ComponentNotRunningError to ComponentStateConflictError 
* Plus replace some usages of RuntimeError for functions which cannot be called while a component is still alive with ComponentStateConflictError
* Replace HTTPClientClosedError with ComponentStateConflictError

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->


FasterSpeeding: This is a pre-cursor to adding ComponentStateConflictError to the voice component btw

Patch:
diff --git a/hikari/errors.py b/hikari/errors.py
index a9420610..4b2ef405 100644
--- a/hikari/errors.py
+++ b/hikari/errors.py
@@ -28,7 +28,7 @@
     "HikariError",
     "HikariWarning",
     "HikariInterrupt",
-    "ComponentNotRunningError",
+    "ComponentStateConflictError",
     "UnrecognisedEntityError",
     "NotFoundError",
     "RateLimitedError",
@@ -39,7 +39,6 @@
     "RESTErrorCode",
     "HTTPError",
     "HTTPResponseError",
-    "HTTPClientClosedError",
     "ClientHTTPResponseError",
     "InternalServerError",
     "ShardCloseCode",
@@ -104,8 +103,12 @@ class HikariInterrupt(KeyboardInterrupt, HikariError):
 
 
 @attr.define(auto_exc=True, repr=False, weakref_slot=False)
-class ComponentNotRunningError(HikariError):
-    """An exception thrown if trying to interact with a component that is not running."""
+class ComponentStateConflictError(HikariError):
+    """Exception thrown when an action cannot be executed in the component's current state.
+
+    Dependent on context this will be thrown for components which are already
+    running or haven't been started yet.
+    """
 
     reason: str = attr.field()
     """A string to explain the issue."""
@@ -222,18 +225,6 @@ class HTTPError(HikariError):
     """The error message."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
-class HTTPClientClosedError(HTTPError):
-    """Exception raised if an `aiohttp.ClientSession` was closed.
-
-    This fires when using a closed `aiohttp.ClientSession` to make a
-    request.
-    """
-
-    message: str = attr.field(default="The client session has been closed, no HTTP requests can occur.", init=False)
-    """The error message."""
-
-
 @typing.final
 class RESTErrorCode(int, enums.Enum):
     """Error codes provided as further info on errors returned by the REST API."""
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 44506cf3..ca3f1349 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -40,6 +40,7 @@
     "GuildPremiumTier",
     "GuildPreview",
     "GuildMemberBan",
+    "GuildNSFWLevel",
     "Member",
     "Integration",
     "IntegrationAccount",
@@ -246,6 +247,23 @@ class GuildVerificationLevel(int, enums.Enum):
     """Must have a verified phone number."""
 
 
+@typing.final
+class GuildNSFWLevel(int, enums.Enum):
+    """Represents the NSFW level of a guild."""
+
+    DEFAULT = 0
+    """Guild has not been categorized yet."""
+
+    EXPLICIT = 1
+    """Guild contains explicit NSFW content."""
+
+    SAFE = 2
+    """Guild is safe of NSFW content."""
+
+    AGE_RESTRICTED = 3
+    """Guild may contain NSFW content."""
+
+
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
 class GuildWidget:
@@ -1189,8 +1207,8 @@ class Guild(PartialGuild, abc.ABC):
     this will be `builtins.None`.
     """
 
-    is_nsfw: bool = attr.field(eq=False, hash=False, repr=False)
-    """Whether the guild is designated as NSFW."""
+    nsfw_level: GuildNSFWLevel = attr.field(eq=False, hash=False, repr=False)
+    """The NSFW level of the guild."""
 
     @property
     def banner_url(self) -> typing.Optional[files.URL]:
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index b73ca1e7..7a67133e 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -355,7 +355,7 @@ def is_alive(self) -> bool:
 
     def _check_if_alive(self) -> None:
         if not self._is_alive:
-            raise errors.ComponentNotRunningError("bot is not running so it cannot be interacted with")
+            raise errors.ComponentStateConflictError("bot is not running so it cannot be interacted with")
 
     async def close(self, force: bool = True) -> None:
         """Kill the application by shutting all components down."""
@@ -565,13 +565,13 @@ def run(
 
         Raises
         ------
-        builtins.RuntimeError
+        hikari.errors.ComponentStateConflictError
             If bot is already running.
         builtins.TypeError
             If `shard_ids` is passed without `shard_count`.
         """
         if self._is_alive:
-            raise RuntimeError("bot is already running")
+            raise errors.ComponentStateConflictError("bot is already running")
 
         if shard_ids is not None and shard_count is None:
             raise TypeError("'shard_ids' must be passed with 'shard_count'")
@@ -733,13 +733,13 @@ async def start(
 
         Raises
         ------
-        builtins.RuntimeError
+        hikari.errors.ComponentStateConflictError
             If bot is already running.
         builtins.TypeError
             If `shard_ids` is passed without `shard_count`.
         """
         if self._is_alive:
-            raise RuntimeError("bot is already running")
+            raise errors.ComponentStateConflictError("bot is already running")
 
         if shard_ids is not None and shard_count is None:
             raise TypeError("'shard_ids' must be passed with 'shard_count'")
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index d1828b0d..5d2c6404 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -136,7 +136,7 @@ class _GuildFields:
     premium_subscription_count: typing.Optional[int] = attr.field()
     preferred_locale: str = attr.field()
     public_updates_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field()
-    is_nsfw: bool = attr.field()
+    nsfw_level: guild_models.GuildNSFWLevel = attr.field()
 
 
 @attr_extensions.with_copy
@@ -1337,7 +1337,7 @@ def _set_guild_attributes(self, payload: data_binding.JSONObject) -> _GuildField
             premium_subscription_count=payload.get("premium_subscription_count"),
             preferred_locale=payload["preferred_locale"],
             public_updates_channel_id=public_updates_channel_id,
-            is_nsfw=payload["nsfw"],
+            nsfw_level=guild_models.GuildNSFWLevel(payload["nsfw_level"]),
         )
 
     def deserialize_rest_guild(self, payload: data_binding.JSONObject) -> guild_models.RESTGuild:
@@ -1367,7 +1367,7 @@ def deserialize_rest_guild(self, payload: data_binding.JSONObject) -> guild_mode
             snowflakes.Snowflake(emoji["id"]): self.deserialize_known_custom_emoji(emoji, guild_id=guild_fields.id)
             for emoji in payload["emojis"]
         }
-        guild = guild_models.RESTGuild(
+        return guild_models.RESTGuild(
             app=self._app,
             id=guild_fields.id,
             name=guild_fields.name,
@@ -1395,7 +1395,7 @@ def deserialize_rest_guild(self, payload: data_binding.JSONObject) -> guild_mode
             description=guild_fields.description,
             banner_hash=guild_fields.banner_hash,
             premium_tier=guild_fields.premium_tier,
-            is_nsfw=guild_fields.is_nsfw,
+            nsfw_level=guild_fields.nsfw_level,
             premium_subscription_count=guild_fields.premium_subscription_count,
             preferred_locale=guild_fields.preferred_locale,
             public_updates_channel_id=guild_fields.public_updates_channel_id,
@@ -1404,7 +1404,6 @@ def deserialize_rest_guild(self, payload: data_binding.JSONObject) -> guild_mode
             roles=roles,
             emojis=emojis,
         )
-        return guild
 
     def deserialize_gateway_guild(self, payload: data_binding.JSONObject) -> entity_factory.GatewayGuildDefinition:
         guild_fields = self._set_guild_attributes(payload)
@@ -1441,7 +1440,7 @@ def deserialize_gateway_guild(self, payload: data_binding.JSONObject) -> entity_
             premium_subscription_count=guild_fields.premium_subscription_count,
             preferred_locale=guild_fields.preferred_locale,
             public_updates_channel_id=guild_fields.public_updates_channel_id,
-            is_nsfw=guild_fields.is_nsfw,
+            nsfw_level=guild_fields.nsfw_level,
             is_large=is_large,
             joined_at=joined_at,
             member_count=member_count,
@@ -1524,7 +1523,7 @@ def _set_invite_attributes(self, payload: data_binding.JSONObject) -> _InviteFie
                 verification_level=guild_models.GuildVerificationLevel(guild_payload["verification_level"]),
                 vanity_url_code=guild_payload["vanity_url_code"],
                 welcome_screen=self.deserialize_welcome_screen(raw_welcome_screen) if raw_welcome_screen else None,
-                is_nsfw=guild_payload.get("nsfw"),
+                nsfw_level=guild_models.GuildNSFWLevel(guild_payload["nsfw_level"]),
             )
             guild_id = guild.id
         elif "guild_id" in payload:
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index f66e45e6..85ef00e5 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -504,7 +504,7 @@ def _acquire_tcp_connector(self) -> aiohttp.TCPConnector:
             _LOGGER.log(ux.TRACE, "acquired new tcp connector")
 
         elif self._tcp_connector.closed:
-            raise errors.HTTPClientClosedError
+            raise errors.ComponentStateConflictError("The client session has been closed, no HTTP requests can occur.")
 
         return self._tcp_connector
 
@@ -524,7 +524,7 @@ def _acquire_client_session(self) -> aiohttp.ClientSession:
             _LOGGER.log(ux.TRACE, "acquired new aiohttp client session")
 
         elif self._client_session.closed:
-            raise errors.HTTPClientClosedError
+            raise errors.ComponentStateConflictError("The client session has been closed, no HTTP requests can occur.")
 
         return self._client_session
 
diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py
index 4c856966..5bb468a4 100644
--- a/hikari/impl/shard.py
+++ b/hikari/impl/shard.py
@@ -542,7 +542,7 @@ async def _send_json(
 
     def _check_if_alive(self) -> None:
         if not self.is_alive:
-            raise errors.ComponentNotRunningError(
+            raise errors.ComponentStateConflictError(
                 f"shard {self._shard_id} is not running so it cannot be interacted with"
             )
 
@@ -589,7 +589,7 @@ async def request_guild_members(
 
     async def start(self) -> None:
         if self._run_task is not None:
-            raise RuntimeError("Cannot run more than one instance of one shard concurrently")
+            raise errors.ComponentStateConflictError("Cannot run more than one instance of one shard concurrently")
 
         run_task = asyncio.create_task(self._run(), name=f"run shard {self._shard_id}")
         self._run_task = run_task
diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py
index d0c8bd1e..2920e866 100644
--- a/hikari/impl/special_endpoints.py
+++ b/hikari/impl/special_endpoints.py
@@ -136,7 +136,7 @@ async def _keep_typing(self) -> None:
                 except asyncio.TimeoutError:
                     pass
 
-        except (asyncio.CancelledError, errors.HTTPClientClosedError):
+        except (asyncio.CancelledError, errors.ComponentStateConflictError):
             pass
 
 
diff --git a/hikari/invites.py b/hikari/invites.py
index dcb0cef6..f9123d38 100644
--- a/hikari/invites.py
+++ b/hikari/invites.py
@@ -136,11 +136,8 @@ class InviteGuild(guilds.PartialGuild):
     welcome_screen: typing.Optional[guilds.WelcomeScreen] = attr.field(eq=False, hash=False, repr=False)
     """The welcome screen of a community guild shown to new members, if set."""
 
-    is_nsfw: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False)
-    """Whether the guild is designated as NSFW.
-
-    This field is currently only returned by the GET Invite REST endpoint.
-    """
+    nsfw_level: guilds.GuildNSFWLevel = attr.field(eq=False, hash=False, repr=False)
+    """The NSFW level of the guild."""
 
     @property
     def splash_url(self) -> typing.Optional[files.URL]:
diff --git a/hikari/traits.py b/hikari/traits.py
index 5f8fe276..dd784301 100644
--- a/hikari/traits.py
+++ b/hikari/traits.py
@@ -501,7 +501,7 @@ def is_alive(self) -> bool:
         """Check whether the bot is running or not.
 
         This is useful as some functions might raise
-        `hikari.errors.ComponentNotRunningError` if this is
+        `hikari.errors.ComponentStateConflictError` if this is
         `builtins.False`.
 
         Returns


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index d6016300..23bf67c1 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -268,7 +268,7 @@ def test_is_alive(self, bot):
     def test_check_if_alive_when_False(self, bot):
         bot._is_alive = False
 
-        with pytest.raises(errors.ComponentNotRunningError):
+        with pytest.raises(errors.ComponentStateConflictError):
             bot._check_if_alive()
 
     def test_check_if_alive(self, bot):
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index df2bcc49..8bc132e2 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2198,7 +2198,7 @@ def deserialize_rest_guild_payload(
             "verification_level": 4,
             "widget_channel_id": "9439394949",
             "widget_enabled": True,
-            "nsfw": True,
+            "nsfw_level": 0,
         }
 
     def test_deserialize_rest_guild(
@@ -2257,7 +2257,7 @@ def test_deserialize_rest_guild(
         assert guild.public_updates_channel_id == 33333333
         assert guild.approximate_member_count == 15
         assert guild.approximate_active_member_count == 7
-        assert guild.is_nsfw is True
+        assert guild.nsfw_level == guild_models.GuildNSFWLevel.DEFAULT
 
     def test_deserialize_rest_guild_with_unset_fields(self, entity_factory_impl):
         guild = entity_factory_impl.deserialize_rest_guild(
@@ -2275,7 +2275,7 @@ def test_deserialize_rest_guild_with_unset_fields(self, entity_factory_impl):
                 "icon": "1a2b3c4d",
                 "id": "265828729970753537",
                 "mfa_level": 1,
-                "nsfw": True,
+                "nsfw_level": 0,
                 "name": "L33t guild",
                 "owner_id": "6969696",
                 "preferred_locale": "en-GB",
@@ -2322,7 +2322,7 @@ def test_deserialize_rest_guild_with_null_fields(self, entity_factory_impl):
                 "max_presences": None,
                 "max_video_channel_users": 25,
                 "mfa_level": 1,
-                "nsfw": False,
+                "nsfw_level": 0,
                 "name": "L33t guild",
                 "owner_id": "6969696",
                 "preferred_locale": "en-GB",
@@ -2410,7 +2410,7 @@ def deserialize_gateway_guild_payload(
             "voice_states": [voice_state_payload],
             "widget_channel_id": "9439394949",
             "widget_enabled": True,
-            "nsfw": False,
+            "nsfw_level": 0,
         }
 
     def test_deserialize_gateway_guild(
@@ -2465,7 +2465,7 @@ def test_deserialize_gateway_guild(
         assert guild.premium_subscription_count == 1
         assert guild.preferred_locale == "en-GB"
         assert guild.public_updates_channel_id == 33333333
-        assert guild.is_nsfw is False
+        assert guild.nsfw_level == guild_models.GuildNSFWLevel.DEFAULT
 
         assert guild_definition.roles == {
             41771983423143936: entity_factory_impl.deserialize_role(
@@ -2537,7 +2537,7 @@ def test_deserialize_gateway_guild_with_unset_fields(self, entity_factory_impl):
                 "system_channel_id": "19216801",
                 "vanity_url_code": "loool",
                 "verification_level": 4,
-                "nsfw": True,
+                "nsfw_level": 0,
             }
         )
         guild = guild_definition.guild
@@ -2598,7 +2598,7 @@ def test_deserialize_gateway_guild_with_null_fields(self, entity_factory_impl):
                 "voice_states": [],
                 "widget_channel_id": None,
                 "widget_enabled": True,
-                "nsfw": False,
+                "nsfw_level": 0,
             }
         )
         guild = guild_definition.guild
@@ -2660,7 +2660,7 @@ def invite_payload(
                 "verification_level": 2,
                 "vanity_url_code": "I-am-very-vain",
                 "welcome_screen": guild_welcome_screen_payload,
-                "nsfw": False,
+                "nsfw_level": 1,
             },
             "channel": partial_channel_payload,
             "inviter": user_payload,
@@ -2697,7 +2697,7 @@ def test_deserialize_invite(
         assert invite.guild.welcome_screen == entity_factory_impl.deserialize_welcome_screen(
             guild_welcome_screen_payload
         )
-        assert invite.guild.is_nsfw is False
+        assert invite.guild.nsfw_level == guild_models.GuildNSFWLevel.EXPLICIT
 
         assert invite.guild_id == 56188492224814744
         assert invite.channel == entity_factory_impl.deserialize_partial_channel(partial_channel_payload)
@@ -2712,12 +2712,10 @@ def test_deserialize_invite(
 
     def test_deserialize_invite_with_unset_guild_fields(self, entity_factory_impl, invite_payload):
         del invite_payload["guild"]["welcome_screen"]
-        del invite_payload["guild"]["nsfw"]
 
         invite = entity_factory_impl.deserialize_invite(invite_payload)
 
         assert invite.guild.welcome_screen is None
-        assert invite.guild.is_nsfw is None
 
     def test_deserialize_invite_with_null_fields(self, entity_factory_impl, partial_channel_payload):
         invite = entity_factory_impl.deserialize_invite(
@@ -2772,7 +2770,7 @@ def invite_with_metadata_payload(
                 "verification_level": 2,
                 "vanity_url_code": "I-am-very-vain",
                 "welcome_screen": guild_welcome_screen_payload,
-                "nsfw": True,
+                "nsfw_level": 0,
             },
             "channel": partial_channel_payload,
             "inviter": user_payload,
@@ -2813,7 +2811,7 @@ def test_deserialize_invite_with_metadata(
         assert invite_with_metadata.guild.welcome_screen == entity_factory_impl.deserialize_welcome_screen(
             guild_welcome_screen_payload
         )
-        assert invite_with_metadata.guild.is_nsfw is True
+        assert invite_with_metadata.guild.nsfw_level == guild_models.GuildNSFWLevel.DEFAULT
 
         assert invite_with_metadata.channel == entity_factory_impl.deserialize_partial_channel(partial_channel_payload)
         assert invite_with_metadata.inviter == entity_factory_impl.deserialize_user(user_payload)
@@ -2861,11 +2859,9 @@ def test_deserialize_invite_with_metadata_with_null_guild_fields(
         self, entity_factory_impl, invite_with_metadata_payload
     ):
         del invite_with_metadata_payload["guild"]["welcome_screen"]
-        del invite_with_metadata_payload["guild"]["nsfw"]
 
         invite = entity_factory_impl.deserialize_invite_with_metadata(invite_with_metadata_payload)
         assert invite.guild.welcome_screen is None
-        assert invite.guild.is_nsfw is None
 
     def test_max_age_when_zero(self, entity_factory_impl, invite_with_metadata_payload):
         invite_with_metadata_payload["max_age"] = 0
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 8f386bb4..26ed3c85 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -507,7 +507,7 @@ def test__acquire_client_session_when_not_None_and_closed(self, rest_client):
         client_session_mock = mock.Mock(closed=True)
         rest_client._client_session = client_session_mock
 
-        with pytest.raises(errors.HTTPClientClosedError):
+        with pytest.raises(errors.ComponentStateConflictError):
             assert rest_client._acquire_client_session() is client_session_mock
 
     def test__acquire_tcp_connector_when_None(self, rest_client):
@@ -539,7 +539,7 @@ def test__acquire_tcp_connector_when_not_None_and_closed(self, rest_client):
         tcp_connector_mock = mock.Mock(closed=True)
         rest_client._tcp_connector = tcp_connector_mock
 
-        with pytest.raises(errors.HTTPClientClosedError):
+        with pytest.raises(errors.ComponentStateConflictError):
             assert rest_client._acquire_tcp_connector() is tcp_connector_mock
 
     @pytest.mark.parametrize(
diff --git a/tests/hikari/impl/test_shard.py b/tests/hikari/impl/test_shard.py
index e5c20f4b..dbe32c16 100644
--- a/tests/hikari/impl/test_shard.py
+++ b/tests/hikari/impl/test_shard.py
@@ -644,7 +644,7 @@ def test_shard_count_property(self, client):
 
     def test_shard__check_if_alive_when_not_alive(self, client):
         with mock.patch.object(shard.GatewayShardImpl, "is_alive", new=False):
-            with pytest.raises(errors.ComponentNotRunningError):
+            with pytest.raises(errors.ComponentStateConflictError):
                 client._check_if_alive()
 
     def test_shard__check_if_alive_when_alive(self, client):
diff --git a/tests/hikari/test_errors.py b/tests/hikari/test_errors.py
index 299e5237..ab0a5b27 100644
--- a/tests/hikari/test_errors.py
+++ b/tests/hikari/test_errors.py
@@ -34,10 +34,10 @@ def test_is_standard_property(self, code, expected):
         assert errors.ShardCloseCode(code).is_standard is expected
 
 
-class TestComponentNotRunningError:
+class TestComponentStateConflictError:
     @pytest.fixture()
     def error(self):
-        return errors.ComponentNotRunningError("some reason")
+        return errors.ComponentStateConflictError("some reason")
 
     def test_str(self, error):
         assert str(error) == "some reason"
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index e179cad1..47c630c3 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -452,7 +452,6 @@ class TestGuild:
     def model(self, mock_app):
         return hikari_test_helpers.mock_class_namespace(guilds.Guild)(
             app=mock_app,
-            is_nsfw=False,
             id=snowflakes.Snowflake(123),
             splash_hash="splash_hash",
             discovery_splash_hash="discovery_splash_hash",
@@ -469,6 +468,7 @@ def model(self, mock_app):
             is_widget_enabled=False,
             max_video_channel_users=10,
             mfa_level=guilds.GuildMFALevel.NONE,
+            nsfw_level=guilds.GuildNSFWLevel.AGE_RESTRICTED,
             owner_id=snowflakes.Snowflake(1111),
             preferred_locale="en-GB",
             premium_subscription_count=12,
@@ -583,7 +583,6 @@ def model(self, mock_app):
             owner_id=snowflakes.Snowflake(1111),
             preferred_locale="en-GB",
             premium_subscription_count=12,
-            is_nsfw=True,
             premium_tier=guilds.GuildPremiumTier.TIER_3,
             public_updates_channel_id=None,
             rules_channel_id=None,
@@ -598,6 +597,7 @@ def model(self, mock_app):
             approximate_member_count=100,
             max_presences=100,
             max_members=100,
+            nsfw_level=guilds.GuildNSFWLevel.AGE_RESTRICTED,
         )
 
     def test_get_emoji(self, model):
@@ -618,7 +618,6 @@ class TestGatewayGuild:
     def model(self, mock_app):
         return guilds.GatewayGuild(
             app=mock_app,
-            is_nsfw=True,
             id=snowflakes.Snowflake(123),
             splash_hash="splash_hash",
             discovery_splash_hash="discovery_splash_hash",
@@ -649,6 +648,7 @@ def model(self, mock_app):
             is_large=True,
             joined_at=None,
             member_count=1,
+            nsfw_level=guilds.GuildNSFWLevel.AGE_RESTRICTED,
         )
 
     def test_channels(self, model):